I'm using both client and server from node-opcua, it's working fine apart for error management on the server side when the client tries to write a value and something goes wrong. In order to improve that, I'd like to have an asynchronous setter like in this example:
const wantedValue = namespace.addAnalogDataItem({
componentOf: node,
browseName: `${chamber}${folder}Req`,
nodeId: `s=${chamber}${folder}Req`,
dataType: "Double",
engineeringUnits: settings.unit,
engineeringUnitsRange: settings.range,
value: {
get: () => new Variant({dataType: DataType.Double, value: wrapper.readProgram(chamber, folder)}),
set: async (variant: Variant) => {
const success = await wrapper.writeProgram(chamber, folder, variant.value)
return success ? StatusCodes.Good : StatusCodes.Bad
}
}
})
This naive approach lead to a typer error:
throw new Error("Cannot find StatusCode " + statusCode);
Is there a better way to do that ?
the get and set lambdas are design to be synchronous.
you need to use the callback form with timestamped_get and timestamped_set to achieve what you need.
Related
I'm using the Nodejs library for talking to Jira called jira-connector. I can get all of the boards on my jira instance by calling
jira.board.getAllBoards({ type: "scrum"})
.then(boards => { ...not important stuff... }
the return set looks something like the following:
{
maxResults: 50,
startAt: 0,
isLast: false,
values:
[ { id: ... } ]
}
then while isLast === false I keep calling like so:
jira.board.getAllBoards({ type: "scrum", startAt: XXX })
until isLast is true. then I can organize all of my returns from promises and be done with it.
I'm trying to reason out how I can get all of the data on pages with Ramda, I have a feeling it's possible I just can't seem to sort out the how of it.
Any help? Is this possible using Ramda?
Here's my Rx attempt to make this better:
const pagedCalls = new Subject();
pagedCalls.subscribe(value => {
jira.board.getAllBoards({ type:"scrum", startAt: value })
.then(boards => {
console.log('calling: ' + value);
allBoards.push(boards.values);
if (boards.isLast) {
pagedCalls.complete()
} else {
pagedCalls.next(boards.startAt + 50);
}
});
})
pagedCalls.next(0);
Seems pretty terrible. Here's the simplest solution I have so far with a do/while loop:
let returnResult = [];
let result;
let startAt = -50;
do {
result = await jira.board.getAllBoards( { type: "scrum", startAt: startAt += 50 })
returnResult.push(result.values); // there's an array of results under the values prop.
} while (!result.isLast)
Many of the interactions with Jira use this model and I am trying to avoid writing this kind of loop every time I make a call.
I had to do something similar today, calling the Gitlab API repeatedly until I had retrieved the entire folder/file structure of the project. I did it with a recursive call inside a .then, and it seems to work all right. I have not tried to convert the code to handle your case.
Here's what I wrote, if it will help:
const getAll = (project, perPage = 10, page = 1, res = []) =>
fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/repository/tree?recursive=true&per_page=${perPage}&page=${page}`)
.then(resp => resp.json())
.then(xs => xs.length < perPage
? res.concat(xs)
: getAll(project, perPage, page + 1, res.concat(xs))
)
getAll('gitlab-examples/nodejs')
.then(console.log)
.catch(console.warn)
The technique is pretty simple: Our function accepts whatever parameters are necessary to be able to fetch a particular page and an additional one to hold the results, defaulting it to an empty array. We make the asynchronous call to fetch the page, and in the then, we use the result to see if we need to make another call. If we do, we call the function again, passing in the other parameters needed, the incremented page number, and the merge of the current results and the ones just received. If we don't need to make another call, then we just return that merged list.
Here, the repository contains 21 files and folders. Calling for ten at a time, we make three fetches and when the third one is complete, we resolve our returned Promise with that list of 21 items.
This recursive method definitely feels more functional than your versions above. There is no assignment except for the parameter defaulting, and nothing is mutated along the way.
I think it should be relatively easy to adapt this to your needs.
Here is a way to get all the boards using rubico:
import { pipe, fork, switchCase, get } from 'rubico'
const getAllBoards = boards => pipe([
fork({
type: () => 'scrum',
startAt: get('startAt'),
}),
jira.board.getAllBoards,
switchCase([
get('isLast'),
response => boards.concat(response.values),
response => getAllBoards(boards.concat(response.values))({
startAt: response.startAt + response.values.length,
})
]),
])
getAllBoards([])({ startAt: 0 }) // => [...boards]
getAllBoards will recursively get more boards and append to boards until isLast is true, then it will return the aggregated boards.
I've been digging around, and I'm not able to find references or documentation on how I can use Asynchronous Functions in Google App Script, I found that people mention It's possible, but not mention how...
Could someone point me in the right direction or provide me with an example?
Promises, Callbacks, or something, that can help me with this.
I have this function lets call it foo that takes a while to execute (long enough that It could time out an HTTP call).
What I'm trying to do Is to refactor it, in a way that it works like this:
function doPost(e) {
// parsing and getting values from e
var returnable = foo(par1, par2, par3);
return ContentService
.createTextOutput(JSON.stringify(returnable))
.setMimeType(ContentService.MimeType.JSON);
}
function foo(par1, par2, par3) {
var returnable = something(par1, par2, par3); // get the value I need to return;
// continue in an Async way, or schedule execution for something else
// and allow the function to continue its flow
/* async bar(); */
return returnable;
}
Now I want to realize that bit in foo because It takes to long and I don't want to risk for a time out, also the logic that occurs there it's totally client Independent, so It doesn't matter, I just need the return value, that I'll be getting before.
Also, I think It's worth mentioning that this is deployed in Google Drive as a web app.
It's been long since this, but adding some context, at that moment I wanted to scheduled several things to happen on Google Drive, and It was timing out the execution, so I was looking for a way to safely schedule a job.
You want to execute functions by the asynchronous processing using Google Apps Script.
You want to run the functions with the asynchronous processing using time trigger.
If my understanding is correct, unfortunately, there are no methods and the official document for directly achieving it. But as a workaround, that can be achieved by using both Google Apps Script API and the fetchAll method which can work by asynchronous processing.
The flow of this workaround is as follows.
Deploy API executable, enable Google Apps Script API.
Using fetchAll, request the endpoint of Google Apps Script API for running function.
When several functions are requested once, those work with the asynchronous processing by fetchAll.
Note:
I think that Web Apps can be also used instead of Google Apps Script API.
In order to simply use this workaround, I have created a GAS library. I think that you can also use it.
In this workaround, you can also run the functions with the asynchronous processing using time trigger.
References:
fetchAll
Deploy the script as an API executable
scripts.run of Google Apps Script API
Benchmark: fetchAll method in UrlFetch service for Google Apps Script
GAS library for running the asynchronous processing
If I misunderstand your question, I'm sorry.
There is another way to accomplish this.
You can use time-based one-off triggers to run functions asynchronously, they take a bit of time to queue up (30-60 seconds) but it is ideal for slow-running tasks that you want to remove from the main execution of your script.
// Creates a trigger that will run a second later
ScriptApp.newTrigger("myFunction")
.timeBased()
.after(1)
.create();
There is handy script that I put together called Async.gs that will help remove the boilerplate out of this technique. You can even use it to pass arguments via the CacheService.
Here is the link:
https://gist.github.com/sdesalas/2972f8647897d5481fd8e01f03122805
// Define async function
function runSlowTask(user_id, is_active) {
console.log('runSlowTask()', { user_id: user_id, is_active: is_active });
Utilities.sleep(5000);
console.log('runSlowTask() - FINISHED!')
}
// Run function asynchronously
Async.call('runSlowTask');
// Run function asynchronously with one argument
Async.call('runSlowTask', 51291);
// Run function asynchronously with multiple argument
Async.call('runSlowTask', 51291, true);
// Run function asynchronously with an array of arguments
Async.apply('runSlowTask', [51291, true]);
// Run function in library asynchronously with one argument
Async.call('MyLibrary.runSlowTask', 51291);
// Run function in library asynchronously with an array of arguments
Async.apply('MyLibrary.runSlowTask', [51291, true]);
With the new V8 runtime, it is now possible to write async functions and use promises in your app script.
Even triggers can be declared async! For example (typescript):
async function onOpen(e: GoogleAppsScript.Events.SheetsOnOpen) {
console.log("I am inside a promise");
// do your await stuff here or make more async calls
}
To start using the new runtime, just follow this guide. In short, it all boils down to adding the following line to your appsscript.json file:
{
...
"runtimeVersion": "V8"
}
Based on Tanaike's answer, I created another version of it. My goals were:
Easy to maintain
Easy to call (simple call convention)
tasks.gs
class TasksNamespace {
constructor() {
this.webAppDevUrl = 'https://script.google.com/macros/s/<your web app's dev id>/dev';
this.accessToken = ScriptApp.getOAuthToken();
}
// send all requests
all(requests) {
return requests
.map(r => ({
muteHttpExceptions: true,
url: this.webAppDevUrl,
method: 'POST',
contentType: 'application/json',
payload: {
functionName: r.first(),
arguments: r.removeFirst()
}.toJson(),
headers: {
Authorization: 'Bearer ' + this.accessToken
}
}), this)
.fetchAll()
.map(r => r.getContentText().toObject())
}
// send all responses
process(request) {
return ContentService
.createTextOutput(
request
.postData
.contents
.toObject()
.using(This => ({
...This,
result: (() => {
try {
return eval(This.functionName).apply(eval(This.functionName.splitOffLast()), This.arguments) // this could cause an error
}
catch(error) {
return error;
}
})()
}))
.toJson()
)
.setMimeType(ContentService.MimeType.JSON)
}
}
helpers.gs
// array prototype
Array.prototype.fetchAll = function() {
return UrlFetchApp.fetchAll(this);
}
Array.prototype.first = function() {
return this[0];
}
Array.prototype.removeFirst = function() {
this.shift();
return this;
}
Array.prototype.removeLast = function() {
this.pop();
return this;
}
// string prototype
String.prototype.blankToUndefined = function(search) {
return this.isBlank() ? undefined : this;
};
String.prototype.isBlank = function() {
return this.trim().length == 0;
}
String.prototype.splitOffLast = function(delimiter = '.') {
return this.split(delimiter).removeLast().join(delimiter).blankToUndefined();
}
// To Object - if string is Json
String.prototype.toObject = function() {
if(this.isBlank())
return {};
return JSON.parse(this, App.Strings.parseDate);
}
// object prototype
Object.prototype.toJson = function() {
return JSON.stringify(this);
}
Object.prototype.using = function(func) {
return func.call(this, this);
}
http.handler.gs
function doPost(request) {
return new TasksNamespace.process(request);
}
calling convention
Just make arrays with the full function name and the rest are the function's arguments. It will return when everything is done, so it's like Promise.all()
var a = new TasksNamespace.all([
["App.Data.Firebase.Properties.getById",'T006DB4'],
["App.Data.External.CISC.Properties.getById",'T00A21F', true, 12],
["App.Maps.geoCode",'T022D62', false]
])
return preview
[ { functionName: 'App.Data.Firebase.Properties.getById',
arguments: [ 'T006DB4' ],
result:
{ Id: '',
Listings: [Object],
Pages: [Object],
TempId: 'T006DB4',
Workflow: [Object] } },
...
]
Notes
it can handle any static method, any method off a root object's tree, or any root (global) function.
it can handle 0 or more (any number) of arguments of any kind
it handles errors by returning the error from any post
// First create a trigger which will run after some time
ScriptApp.newTrigger("createAsyncJob").timeBased().after(6000).create();
/* The trigger will execute and first delete trigger itself using deleteTrigger method and trigger unique id. (Reason: There are limits on trigger which you can create therefore it safe bet to delete it.)
Then it will call the function which you want to execute.
*/
function createAsyncJob(e) {
deleteTrigger(e.triggerUid);
createJobsTrigger();
}
/* This function will get all trigger from project and search the specific trigger UID and delete it.
*/
function deleteTrigger(triggerUid) {
let triggers = ScriptApp.getProjectTriggers();
triggers.forEach(trigger => {
if (trigger.getUniqueId() == triggerUid) {
ScriptApp.deleteTrigger(trigger);
}
});
}
While this isn't quite an answer to your question, this could lead to an answer if implemented.
I have submitted a feature request to Google to modify the implementation of doGet() and doPost() to instead accept a completion block in the functions' parameters that we would call with our response object, allowing additional slow-running logic to be executed after the response has been "returned".
If you'd like this functionality, please star the issue here: https://issuetracker.google.com/issues/231411987?pli=1
I am trying to connect to a database using the code below:
import SQLite from 'react-native-sqlite-storage'
var db = SQLite.openDatabase({name : "banco.db", createFromLocation : 1}, this.successCB(), this.errorCB());
errorCB() {
this.setState({message: "I NEED SHOW THE ERROR HERE"});
}
successCB() {
this.setState({message: "SQL executed fine"});
}
How to show the error on the errorCB function?
This is in the documentation example. The error callback will get passed an argument containing the error. You are also not providing the correct value to openDatabase. You should be passing in functions, not trying to call the function.
Copy pasting the relevant parts from the documentation with comments to explain:
// Your error callback function that should take an argument that will contain your error.
errorCB(err) {
console.log("SQL Error: " + err);
// Here you can use err in your setState call.
}
openCB() {
console.log("Database OPENED");
}
// openDatabase should be passed in the functions; openCB and errorCB in this example.
var db = SQLite.openDatabase("test.db", "1.0", "Test Database", 200000, openCB, errorCB);
// What you're doing is incorrect as it's akin to doing this which is wrong.
// var db = SQLite.openDatabase("test.db", "1.0", "Test Database", 200000, openCB(), errorCB());
This is really more of a basic JavaScript question where you need to be able to read the documentation and understand how to use a given API. If you're having trouble with this, I suggest reading up about Higher-Order Functions as it's fundamental to JavaScript.
Edit: To be very direct and answer the comments; this is what your code should look like:
import SQLite from 'react-native-sqlite-storage'
var db = SQLite.openDatabase({name : "banco.db", createFromLocation : 1}, this.successCB, this.errorCB);
// Making the assumption that these are in a class,
// otherwise add the const keyword before them.
// Convert these to arrow functions instead
// so they can more easily be passed as variables.
errorCB = (err) => {
this.setState({message: err});
}
successCB = () => {
this.setState({message: "SQL executed fine"});
}
Given your comments, I'll be very direct here. If you don't understand how functions, Higher-Order Functions, and variables/values work in JavaScript, you are going to have a very difficult time with React Native. Especially so if you are unfamiliar with ES6 syntax. Go through the book in that link or one of the many other great resources for learning the fundamentals of JavaScript before tackling React Native.
I used to open SQLite using following statement
database = SQLite.openDatabase({name: 'my.db', location: 'default'}, (db) => {
db.transaction( tx => {
tx.executeSql(`CREATE TABLE IF NOT EXISTS tableName (columnNames)`);
}, error => {
this.setState({message: error});
});
}, error => {
this.setState({message: error});
});
Hope this will help!
Context
I have a basic PipeTransform, expect the fact that it is async. Why? because I have my own i18n service (because of parsing, pluralization and other constraints, I did my own) and it returns a Promise<string>:
#Pipe({
name: "i18n",
pure: false
})
export class I18nPipe implements PipeTransform {
private done = false;
constructor(private i18n:I18n) {
}
value:string;
transform(value:string, args:I18nPipeArgs):string {
if(this.done){
return this.value;
}
if (args.plural) {
this.i18n.getPlural(args.key, args.plural, value, args.variables, args.domain).then((res) => {
this.value = res;
this.done = true;
});
}
this.i18n.get(args.key, value, args.variables, args.domain).then((res) => {
this.done = true;
this.value = res;
});
return this.value;
}
}
This pipe works well, because the only delayed call is the very first one (the I18nService uses lazy loading, it loads JSON data only if the key is not found, so basically, the first call will be delayed, the other ones are instant but still async).
Problem
I can't figure out how to test this pipe using Jasmine, since it is working inside a component I know it works, but the goal here is to get this fully tested using jasmine, this way I can add it to a CI routine.
The above test:
describe("Pipe test", () => {
it("can call I18n.get.", async(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
expect(pipe.transform("nope", {key: 'test', domain: 'test domain'})).toBe("test value");
})));
});
Fails because since the result given by the I18nService is async, the returned value is undefined in a sync logic.
I18n Pipe test can call I18n.get. FAILED
Expected undefined to be 'test value'.
EDIT: One way to do it would be to use setTimeout but I want a prettier solution, to avoid adding setTimeout(myAssertion, 100) everywhere.
Use fakeAsync from #angular/core/testing. It allows you to call tick(), which will wait for all currently queued asynchronous tasks to complete before continuing. This gives the illusion of the actions being synchronous. Right after the call to tick() we can write our expectations.
import { fakeAsync, tick } from '#angular/core/testing';
it("can call I18n.get.", fakeAsync(inject([I18n], (i18n:I18n) => {
let pipe = new I18nPipe(i18n);
let result = pipe.transform("nope", {key: 'test', domain: 'test domain'});
tick();
expect(result).toBe("test value");
})));
So when should we use fakeAsync and when should we use async? This is the rule of thumb that I go by (most of the time). When we are making asynchronous calls inside the test, this is when we should use async. async allows to test to continue until all asynchronous calls are complete. For example
it('..', async(() => {
let service = new Servce();
service.doSomething().then(result => {
expect(result).toBe('hello');
});
});
In a non async test, the expectation would never occur, as the test would complete before the asynchronous resolution of the promise. With the call to async, the test gets wrapped in a zone, which keeps track of all asynchronous tasks, and waits for them to complete.
Use fakeAsync when the asynchronous behavior is outside the control of the test (like in your case is going on in the pipe). Here we can force/wait for it to complete with the call to tick(). tick can also be passed a millisecond delay to allow more time to pass if needed.
Another option is to mock the service and make it synchronous, as mentioned in this post. When unit testing, if your components in test are dependent on heavy logic in the service, then the component in test is at the mercy of that service working correctly, which kinda defeats the purpose of a "unit" test. Mocking makes sense in a lot of cases.
Any user (client) making a research on my meteor app would call a meteor method that will send asynchronously results to this client when it founds something ( could be by searching many different external apis for exemple ) and possibly would inject it in a client-side only (none persistent) collection. how ?
From the server methods, you can pass error or results and they don't have to be from a collection.
Inside methods:
methodName: function (arg1, arg2){
if(arg1 !== 'something'){
throw new Meteor.Error(400, 'Error text.')
}
//do something if there is no error here. You don't have to check if there are no errors here as Meteor doesn't jump to DB actions/return a result if there is an error.
return 'result you want to show. Could be an object, string, a document from the DB etc.'
}
Your call on the client side
Meteor.call('methodName', arg1, arg2, function (err, res){
if(err){
//do something with the err
} else {
//do something with the result
}
});
Next time, please check the documentation before asking something. Cheers.