This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 8 years ago.
I need to chain some asynch actions. I'm trying to write Parse.com cloud code which uses express. (I think express is where the promises support originates). I see why promises are valuable, but am still unsure about a couple things. The first is how to collect the results of sequenced actions. The code below illustrates:
function doAsnychThingsInSequence(params) {
return doThing0(params).then(function(resultOfThing0) {
// thing1 depends on resultOfThing0
doThing1(resultOfThing0);
}).then(function(resultOfThing1) {
// here is where I am confused.
// thing2 depends on the results of thing0 and thing1
doThing2(resultOfThing0 /* out of scope?? */, resultOfThing1);
}, function(error) {
// handle error
});
}
After thing1 is done, I need the results of both actions. I think I can allocate a variable at the top of the function and assign it to the first result in the first callback, is that the right way? I guess, at the heart of my confusion is question two...
return doThing0(params0).then(function(resultOfThing0) {
doThing1(resultOfThing0);
// what does return mean here? does what I return here relate to the
// parameters of the next function?
return "foo";
}).then(function(param) {
// what is in param? is it "foo"?
}, function(error) {
});
Promises can be imagined as a stream processing: one function gets input, does something with it and pass it to the next function in the chain.
So if you need to pass input (for the chain) parameters further you should include them into
the output data so next one in the chain can use them:
function doThing1(params1) {
...
return [params1, result1];
}
function doThing2(params1, params2) {
...
}
As you've mentioned you can use some variable outside doThing1 and doThing2 but it will make these functions statefull that may lead to side effects of various kinds. Not desired in general for async processing.
The function supplied to then() must return a value, whether that value is the value to be used, or a promise for an upcoming value. Either way (per the promises spec) then() will return a new promise. That's the first issue I see with your code.
Next is that you have to store thing1 in a higher scope so that you can access it later. So something like this might be in order:
// example
var thing1;
getThing1()
.then(function(value){
thing1 = value; // <-- store in higher scope
return getThing2(); // <-- return
})
.then(function(value){
thing2 = value;
// now you have both thing1 and thing2 in scope
})
I'd like to summarize what I've learned from the two helpful answers from #greim and #c-smile. +1 both for the kind help. But If I'm understanding correctly, then this is a disappointment for me about Promises.
#greim's answer will make my callbacks refer to local variables in the containing function. But this is disappointing because it makes the callback dependent those locals. It would be hard, for example, to pass in a callback.
#c-smile's answer will make my functions return an array (or some collection) of their parameters and their result. All callers of that function would then be expected to dig through the return value for the "natural" result as opposed to the params that it used to get the result.
Restating the answers in terms of my original post:
// #greim
function doAsnychThingsInSequence(params) {
var theResultOfThing0; // yuck, for needing to make up a non-colliding variable name
return doThing0(params).then(function(resultOfThing0) {
theResultOfThing0 = resultOfThing0;
return doThing1(resultOfThing0);
}).then(function(resultOfThing1) {
return doThing2(theResultOfThing0, resultOfThing1);
}, function(error) {
// handle error
});
}
// #c-smile
function doThing1(params) { // params are resultOfThing0
// do stuff
// the "natural" answer is just resultOfThing1, but ...
return [params, resultOfThing1];
}
// if this function was doThingN, then we'd have
return [params0, params1 ... paramsN, resultOfThingN]; // yikes!
// at least this method looks great now, how I want it to look...
function doAsnychThingsInSequence(params) {
return doThing0(params).then(function(resultOfThing0) {
return doThing1(resultOfThing0);
}).then(function(resultOfThing0, resultOfThing1) {
return doThing2(resultOfThing0, resultOfThing1);
}, function(error) {
// handle error
});
}
Related
I want to create a class whose duty is to poll data sources, collate information into an array of 'alert' objects, and then deliver a subset of those alerts to any other class that wants them.
Because polling happens asynchronously (I'm requesting data from a web service) then I assume that what I actually need to return is a promise which, when fulfilled, will give the correct subset of Alert objects.
But clearly I don't understand how to do this, because the method that is supposed to return the promise returns something else.
Here's my code so far. As you can see, I'm trying to store the promise in an instance attribute and then retrieve it:
export class AlertCollection {
constructor() {
this.alerts = null;
}
// poll the data sources for alert data; store a promise that resolves
// to an array of alerts
poll() {
this.alerts = this.pollTeapot()
.then( (arr) => {this.pollDeliverance(arr);} );
}
// return a promise that fulfils to an array of the alerts you want
filteredAlerts(filter) {
return this.alerts; // not filtering for now
}
// return a promise that fulfills to the initial array of alerts
pollTeapot() {
let process = (json) => {
json2 = JSON.parse(json);
return json2.map( (a) => new Alert(a) );
};
message = new MessageHandler("teapot", "alerts")
return message.request().then( (json) => {process(json);} );
}
// Modify the alerts based on the response from Deliverance.
// (But for the time being let's not, and say we did.)
pollDeliverance(alerts) {
return alerts;
}
}
message.request() returns a promise from the web service. That works. If I snapshot the process function inside pollTeapot() I get the right data.
But, if I snapshot the return value from filteredAlerts() I don't get that. I don't get null either (which would at at least make sense, although it would be wrong.) I get something like { _45: 0, _81: 0, _65: null, _54: null }.
Any pointers would be very much appreciated at this point. (This is in React Native, by the way, if that helps.)
I am not sure if I understood your problem fully, but I will try to give you an generic solution to chaining promises one after another.
someAsyncFunction().then(dataFromAsync1 => {
return anotherAsyncFunction(dataFromAsync1).then(dataFromAsync2 => {
return doSomethingWithData(dataFromAsync1, dataFromAsync2);
});
});
This is going to be a hard one to describe - I have a working example but it's convoluted in that I've had to "mock up" all of the async parts, and use function classes rather than the class keyword - but the idea is the same!
There are 2 parts to this answer.
It does not make sense to store alerts as an instance variable. They are asynchronous, and wont exist until after the async calls have completed
You'll need to chain all of your behaviour onto the initial call to poll
In general, you chain promises on to one another like this
functionWhichReturnsPromise()
.then(functionPointer)
.then(function(result){
// some functionality, which can return anything - including another promise
});
So your code would end up looking like
var alertCollection = new AlertCollection()
alertCollection.poll().then(function(alerts){
//here alerts have been loaded, and deliverance checked also!
});
The code of that class would look along the lines of:
export class AlertCollection {
constructor() {
}
// poll the data sources for alert data; store a promise that resolves
// to an array of alerts
poll() {
return this.pollTeapot()
.then(filteredAlerts)
.then(pollDeliverance);
}
// return a promise that fulfils to an array of the alerts you want
filteredAlerts(alerts) {
return alerts; // not filtering for now
}
// return a promise that fulfills to the initial array of alerts
pollTeapot() {
let process = (json) => {
json2 = JSON.parse(json);
return json2.map( (a) => new Alert(a) );
};
message = new MessageHandler("teapot", "alerts")
return message.request().then(process);
}
// Modify the alerts based on the response from Deliverance.
// (But for the time being let's not, and say we did.)
pollDeliverance(alerts) {
return alerts;
}
}
A few notes
filteredAlerts can do whatever you like, so long as it returns an array of results
pollDeliverance can also do whatever you like - if it needs to call another async method, remember to return a promise which resolves to an array of alerts - perhaps updated from the result of the async call.
I have created a JSFiddle which demonstrates this - using a simple getJSON call to replicate the async nature of some of this. As I mentioned, it is convoluted, but demonstrates the process:
Live example: https://jsfiddle.net/q1r6pmda/1/
Following the documentation, I am using this in my JS:
var store = StoreClient('my secret');
store.set('active', true);
var status = store.get('active');
The variable status never has a value. I'm clearly not using the library correctly.
For context, this is inside a switch statement that does something like this for many of the cases, where some of them need to set or get a value from the StoreClient.
The documentation uses this example:
var store = StoreClient('your secret here');
store
.set('hello', 'world')
.then(function() {
return store.get('hello');
})
.then(function(value) {
// value === 'world'
return store.delete('hello');
})
.then(function() {
callback();
})
.catch(callback);
Because I'm on the amateur side, I'm not super familiar with promises. In that example, it's unclear to me which parts of the are required in order to [a] set, and eventually, [b] get a value. I suggest including an example that doesn't have set/get/delete combined into one.
I tried this:
var store = StoreClient('my secret');
store
.set('active', true)
.then(function() {
return store.get('active');
})
.then(function() {
callback();
})
.catch(callback);
... but then I get an error that there is no output variable, even though I haven't touched the output variable at the bottom of the script.
David from Zapier's Platform team here.
Sorry about the confusion in the docs. I'll give you a quick answer on how to fix your code and a long one as to why. In the meantime, I'll make a note to update the docs with more sensical examples.
Short
Two big things:
A. Promises pick whatever was returned in the last function. If you don't bring them along, they're lost. Your code should read:
.then(function(storedVal) { // <- that variable is missing in your code
console.log('stored val is', storedVal);
})
B. You need to provide a value to the second argument of callback. There's a better example here.
.then(function(storedVal) {
callback(null, {active: storedVal});
})
Long
Here's some of the nitty gritty on how to make all Zapier code work great.
Callback
Your code runs inside AWS Lambda, which always needs to know when you're finished. It executes all of your code in a special function with a certain set of arguments. The pertinent one here is callback, a function that you can call when you're ready to exit (or have an error). You can read more about that setup here.
Like most node callbacks, the callback has the function signature callback (error, result). To throw an error, you pass something in the first spot:
callback({msg: 'thing went wrong'});
To pass a result, use the second (and nothing in the first)
callback(null, {myData: 4});
So, not passing anything there is why the zap result isn't seeing any data.
Promises
In general, callbacks suck and are confusing to work with, so we designed StoreClient to return promises. There's a lot of materials about promises online so I won't go into the details here. The important thing is that whatever gets returned from a promise's function is the argument in the next one. For example:
Promise.resolve(1)
.then(function(val) {
// val === 1
return Promise.resolve(val + 1)
})
.then(function(val) {
// val === 2
})
There's a more practical example in these docs:
var store = StoreClient('your secret here');
var outCount;
store
.get('some counter')
.then(function(count) {
count = (count || 0) + 1;
outCount = count;
return store.set('some counter', count);
})
.then(function() {
callback(null, {'the count': outCount});
})
.catch(callback);
Hopefully that clears things up a bit!
Also, if you want to give Python a try, you can do the same code, but much simpler (example here).
Either way, let us know if there's anything else we can do to help!
I have read the following questions and corresponding insightful answers and understood how asynchronous call back functions work-
How do I return the response from an asynchronous call?
How to return value from an asynchronous callback function?
Returning value from asynchronous JavaScript method?
But still not able to successfully return the appropriate vaule. Following is the code -
function foo(callback){
request('some-url.json', function (error, response, body) {
//Check for error
if(error){
//error body
}
//Check for right status code
if(response.statusCode !== 200){
//response body
}
//All is good. Print the data
var data = JSON.parse(body);
callback(data.some-property);
});
}
module.exports = {
foo:foo(function(result){
console.log(result); //works
return result; //doesn't work
}),
};
I am able to get the expected result in the console log but still its not able to return the value.
Error: TypeError: Property 'foo' of object #<Object> is not a function
Problem:
1.Is the callback function execution correct?
2.What should I do to return the value successfully?
Edit: Finally after an extensive conversation with Paarth I have decided to resort to promise.js which will allow me to indirectly return the value I seek from my function. For further resources regarding promise.js -
https://gist.github.com/domenic/3889970
https://www.promisejs.org/
http://colintoh.com/blog/staying-sane-with-asynchronous-programming-promises-and-generators
Also worth mentioning : Bluebird & q
There's no real way to go from asynchronous code back to synchronous code. You can get close by using promises which treat delayed operations as data so you can return the promise object and set up actions that will happen on it later, but with callbacks you have to just keep creating callbacks.
You can find some basic information on promises here https://www.promisejs.org/ (though I'm not endorsing promise.js) and more here.
If you're working in ES5 Bluebird and Q are well known promise libraries. Promises are native functionality in ES6.
So ignoring the module syntax let's say you're trying to use that function.
function something() {
...
// result not bound, foo is not called
foo(function(result){
console.log(result); //result was passed into this callback and is bound
return result; //result is still bound but returning does nothing. No one is waiting for the return value.
});
// code below this point probably already executed before your console.log in the foo callback. No one is waiting for the return.
}
Responding directly to your questions:
Yes, you called foo correctly until you tried to return something. Returning isn't really a concept that applies to callback functions. You can only pass the data on to other callbacks.
You don't ever return the value.
Let's say I have foo, which takes in a callback. If I want to act on foo's result in another function, foo2, I have to call
function foo2 () {
foo(function(result) {
//act on result
}
}
Now let's say I want foo2 to return foo1's result to yet another function. Well, with callbacks, I can't. I can only ask foo2 to take in yet another callback and pass on the data.
function foo2 (callback) {
foo(function(result) {
callback(result);
}
}
That's because module.exports won't wait to the request (asynchronous) to finish. Simply export the function and call it somwhere when you want.
You can export the function :
//yourController.js
module.exports = foo;
Now require that function somewhere in other file and call it :
// another.js
var foo = require('yourController.js');
foo(function(result){
// your result is here
});
Or
require('yourController.js')(function(result){/* Do whatever with result */});
First of all, I completely read all the answers on this question, but despite that I come to realize that after years of scripting I ended up in the aSync hell.
I have a method that uses an async function. Depending on the result of that function, the method should return true or false.
So, simply said:
example = {
overview: undefined,
aSyncFunction: function (callback) {
// Adds values to overview, which we will use in otherFunction
callback();
return this;
},
otherFunction: function (data) {
var result = false;
this.aSyncFunction( function () {
var available = this.overview[data.name];
// result == filter overview with supplied data)
}.bind(this));
return result;
}
};
I've created a JsFiddle to show you the exact situation I'm in: https://jsfiddle.net/copt436a/1/
Deleting the setTimeOut will deliver true, otherwise it is false.
Note, I'm cannot change aSyncFunction at the moment, so the solution must be somewhere in otherFunction
I tried to separate the callback function in a different function, but in that case the return value is stuck in that particular function otherFunction keeps returning undefined. Also using the return value of aSyncFunction does not give me the result I want, cause this returns this.
I'm completely stuck on this one, and probably the solution is quite simple.
tl;dr : I'm looking for a way to have the first .then callback make changes to the data that is passed to subsequent chained events.
I have a library that encapsulates some async operations.
dudetools.getDude(2); // causes an XHR against REST resource "Dude" for row id 2
For awesomeness purposes, dudetools.getDude returns the promise created by the underlying $.ajax call. Thus, I can do things like:
dudetools.getDude(dudeId).done(function(dudeData) { /* do stuff with dude's data */ });
Now I'm trying to modify dudetools so that it'll do some convenient data-massaging on response data before continuing along the promise chain. I want this massage to happen universally, without calling code having to request it or even know about it.
Because the dudetools implementation can't share a closure with all calling code, I'm hoping to leverage the fact that, in JavaScript, non-scalars are always passed by reference rather than by value.
Consider:
var urStuff = {};
function wreck(x) {
x.isWrecked = 'so wrecked';
}
wreck(urStuff);
// urStuff.isWrecked === 'so wrecked' ^.^
I dare you to try it.
So, I was hoping this would work:
dudetools = {
'getDude': function(dudeId) {
return $.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
// I'm so clever!
dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
});
}
}
Of course, it doesn't work. My clever code is being executed (I've seen it), and it's reaching the right conclusions, but subsequent Deferred events in the chain never see the modifications. I.e.:
$.when(
dudetools.getDude(dudeId)
).done(function(mysteriouslyUnmodifiedInfo) {
/* the info passed to this function is mysteriously unmodified! HALP */
});
Any suggestions? Is there a way to accomplish what I'm after?
Also: I'm still kind of new to promises in general, and my grasp of the differences between Deferreds, Promises, and associated constructs is still kind of fuzzy, so I'd very much appreciate your efforts to be clear and explicit when explaining to me how I've ruined everything.
Thanks very much.
EDIT: updated to reflect fact that dudetools.getDude returns a promise, not a Deferred. Because I now (mostly) understand the difference.
The magic of .then is that it pipes its return value into the next callbacks param.
If you don't return your object (even if you haven't changed anything), then undefined is returned by default.
do_something()
.then(function (json) { return JSON.parse(json); })
.then(function (response) { return response.data; })
.then(function (data) { data.tweaked = true; return data; });
You'll want to return your own new Deferred.promise() object.
http://api.jquery.com/deferred.promise/
dudetools = {
'getDude': function(dudeId) {
var dfd = new jQuery.Deferred();
$.ajax('/api/Dude/' + dudeId).then(function(dudeData) {
dudeData.isDuplicated = dudeData.isDuped && dudeData.drillDown > 5;
// I'm so clever!
dfd.resolve(dudeData);
});
return dfd.promise();
}
}
Hope that helps.