I'm trying to call an async function to get the events as a function using a promise and this code is "working" as expected (I get the events in the console.log), only that FullCalendar is not waiting for the successCallback, it is simply displaying an empty calendar.
events: function(fetchInfo,successCallback){
getAllEvents({StartDate:fetchInfo.start,EndDate:fetchInfo.end})
.then((returnEvents) => {
console.log(returnEvents);
successCallback(returnEvents);
})
.catch((error) => {
console.log(error);
})
} ,
Assistance is greatly appreciated.
Got it, it was working correctly, just didn't have the return events set as an Array.
I've always struggled to get my head around Redux-thunk, as it really don't understand what great purpose it serves. For example, here's a random Redux-Thunk example I found from a website:
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
setTimeout(() => {
dispatch(addTodoSuccess(res.data));
}, 2500);
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
It's seemingly simple, addTodo is a function that takes in the title and userId and returns a function with dispatch as a parameter, which then uses dispatch once and then again for the response of the HTTP request. Because in this case Redux-Thunk is being used, you would just do dispatch(addTodo(x,x));
Why would I not just do something like this though?
function addTodo(dispatch, title,userId){
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
setTimeout(() => {
dispatch(addTodoSuccess(res.data));
}, 2500);
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
}
Then from anywhere, I can just call addTodo(dispatch, x, x);
Why would I use the Redux-Thunk example over my own?
Here are few points through which i will try to explain why should go with redux-thunk.
Its middle ware so it will make dispatch and state object available in every action you define without touching you component code.
When you pass dispatch function which is either from props or from mapDispatchToProps(react-redux) which creates closure. This closure keeps memory consumed till asyc operation finished.
When ever you want to dispatch any action, after completing or in async operation you need to pass dispatch function and in this case you need to modify two files like your component and actions.
If something is already available and tested with lot effort and community support why not use it.
your code will be more readable and modular.
Worst case for both approach, say after completing project, need to change thunk approach, you can easily mock thunk middle ware with your custom middle ware code and resolve it but in case of passing dispatch function it will refactoring all code and search and replace and find way to manage it.
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
My ultimate goal is to assign a chunk of database to class based object. To do this I am utilizing promises in typescript with the help of the .then() function to chain them. However I am running into brick walls.
return(this.httpService.post(someurl, somepayload, someheaders)
.toPromise()
.then(response => response.json())
.then(MyCustomClass.function(MyObject)));
However when this code executes the .then(MyCustomClass.function(MyObject)) before it gets the response.json() which is causing issues in my program.
My question is, why are they occurring in that order and is there any way I can force them to execute in sequence?
You're calling MyCustomClass.function(MyObject), and passing the returned value to then(). What you actually want is to pass a function that, when called, will execute MyCustomClass.function(MyObject):
.then(() => {
MyCustomClass.function(MyObject);
}));
I am trying to pass a value in the callback of an async meteor method. "mongoCollections" is global variable
// Async method
let waiter = function(cb) {
setTimeout(() => {
cb(undefined, {data: 'test', other: mongoCollections})
}, 1000);
}
// Meteor method
Meteor.methods({
'getCollections': () => {
let func = Meteor.wrapAsync(waiter);
let res = func();
return res;
}
});
On the client
Meteor.call('getCollections', (err, res) => {
console.log(err, res)
});
The issue is that in its current state the client callback is not fired, no error or anything.
But if I remove the "other: mongoCollections" part of the object then the callback is fired. Why would sending mongoCollections prevent the callback from being fired at all? If there is an error how can I catch it?
My guess is that you are loosing your context between waiter() and the execution of cb(), which means that mongoCollections is undefined in cb(), thus the call fails.
Try to log mongoCollections in the anonymous function that you setTimeout for. It will probably show as undefined.
Or try it like this:
let waiter = function(cb) {
var _mongoCollections = mongoCollections;
setTimeout(() => {
cb(undefined, {data: 'test', other: _mongoCollections})
}, 1000);
}
(which puts mongoCollections in the closure instsead)
Another possibility (based on comment below): Your mongoCollections object is not serializable. You can try it by logging the result of JSON.stringify(mongoCollections). If this fails you have to extract the parts of the object you need, that can be serialized.
There are a number of things that could be happening here, but my guess is that an error is occurring somewhere and the error message is getting swallowed by a handler somewhere deeper.
You probably want to be using Meteor.setTimeout instead of vanilla setTimeout at a minimum. Have a look here: http://docs.meteor.com/api/timers.html#Meteor-setTimeout
Beyond that, I would follow the previous answerer's advice and try to make sure that mongoCollections is as global as you think it is. If the only change between the callback working and not working is the addition of a single symbol, then the culprit is likely that your added symbol is undefined.