I've recently run into a certain situation a couple of times, which I didn't know how to solve properly. Assume the following code:
somethingAsync()
.then( afterSomething )
.then( afterSomethingElse )
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
}
Now a situation might arise where I would want to have access to amazingData
in afterSomethingElse
.
One obvious solution would be to return an array or a hash from afterSomething
, because, well, you can only return one value from a function. But I'm wondering if there is way to have afterSomethingElse
accept 2 parameters and invoke it likewise, as that seems a lot easier to document and understand.
I'm only wondering about this possibility since there is Q.spread
, which does something similar to what I want.
You can't resolve a promise with multiple properties just like you can't return multiple values from a function. A promise conceptually represents a value over time so while you can represent composite values you can't put multiple values in a promise.
A promise inherently resolves with a single value - this is part of how Q works, how the Promises/A+ spec works and how the abstraction works.
The closest you can get is use Q.spread
and return arrays or use ES6 destructuring if it's supported or you're willing to use a transpilation tool like BabelJS.
As for passing context down a promise chain please refer to Bergi's excellent canonical on that.
You can return an object containing both values — there's nothing wrong with that.
Another strategy is to keep the value, via closures, instead of passing it through:
somethingAsync().then(afterSomething);
function afterSomething(amazingData) {
return processAsync(amazingData).then(function (processedData) {
// both amazingData and processedData are in scope here
});
}
Fully rather than partially inlined form (equivalent, arguably more consistent):
somethingAsync().then(function (amazingData) {
return processAsync(amazingData).then(function (processedData) {
// both amazingData and processedData are in scope here
});
}
you can only pass one value, but it can be an array with multiples values within, as example:
function step1(){
let server = "myserver.com";
let data = "so much data, very impresive";
return Promise.resolve([server, data]);
}
on the other side, you can use the destructuring expression for ES2015 to get the individual values.
function step2([server, data]){
console.log(server); // print "myserver.com"
console.log(data); // print "so much data, very impresive"
return Promise.resolve("done");
}
to call both promise, chaining them:
step1()
.then(step2)
.then((msg)=>{
console.log(msg); // print "done"
})
Two things you can do, return an object
somethingAsync()
.then( afterSomething )
.then( afterSomethingElse );
function processAsync (amazingData) {
//processSomething
return {
amazingData: amazingData,
processedData: processedData
};
}
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( dataObj ) {
let amazingData = dataObj.amazingData,
processedData = dataObj.proccessedData;
}
Use the scope!
var amazingData;
somethingAsync()
.then( afterSomething )
.then( afterSomethingElse )
function afterSomething( returnedAmazingData ) {
amazingData = returnedAmazingData;
return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
//use amazingData here
}
Simply make an object and extract arguments from that object.
let checkIfNumbersAddToTen = function (a, b) {
return new Promise(function (resolve, reject) {
let c = parseInt(a)+parseInt(b);
let promiseResolution = {
c:c,
d : c+c,
x : 'RandomString'
};
if(c===10){
resolve(promiseResolution);
}else {
reject('Not 10');
}
});
};
Pull arguments from promiseResolution.
checkIfNumbersAddToTen(5,5).then(function (arguments) {
console.log('c:'+arguments.c);
console.log('d:'+arguments.d);
console.log('x:'+arguments.x);
},function (failure) {
console.log(failure);
});
Here is how I reckon you should be doing.
splitting the chain
Because both functions will be using amazingData, it makes sense to have them in a dedicated function.
I usually do that everytime I want to reuse some data, so it is always present as a function arg.
As your example is running some code, I will suppose it is all declared inside a function. I will call it toto().
Then we will have another function which will run both afterSomething() and afterSomethingElse().
function toto() {
return somethingAsync()
.then( tata );
}
You will also notice I added a return statement as it is usually the way to go with Promises - you always return a promise so we can keep chaining if required. Here, somethingAsync() will produce amazingData and it will be available everywhere inside the new function.
Now what this new function will look like typically depends on is processAsync() also asynchronous?
processAsync not asynchronous
No reason to overcomplicate things if processAsync() is not asynchronous. Some old good sequential code would make it.
function tata( amazingData ) {
var processed = afterSomething( amazingData );
return afterSomethingElse( amazingData, processed );
}
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}
Note that it does not matter if afterSomethingElse() is doing something async or not. If it does, a promise will be returned and the chain can continue. If it is not, then the result value will be returned. But because the function is called from a then(), the value will be wrapped into a promise anyway (at least in raw Javascript).
processAsync asynchronous
If processAsync() is asynchronous, the code will look slightly different. Here we consider afterSomething() and afterSomethingElse() are not going to be reused anywhere else.
function tata( amazingData ) {
return afterSomething()
.then( afterSomethingElse );
function afterSomething( /* no args */ ) {
return processAsync( amazingData );
}
function afterSomethingElse( processedData ) {
/* amazingData can be accessed here */
}
}
Same as before for afterSomethingElse(). It can be asynchronous or not. A promise will be returned, or a value wrapped into a resolved promise.
Your coding style is quite close to what I use to do, that is why I answered even after 2 years.
I am not a big fan of having anonymous functions everywhere. I find it hard to read. Even if it is quite common in the community.
It is as we replaced the callback-hell by a promise-purgatory.
I also like to keep the name of the functions in the then short. They will only be defined locally anyway.
And most of the time they will call another function defined elsewhere - so reusable - to do the job.
I even do that for functions with only 1 parameter, so I do not need to get the function in and out when I add/remove a parameter to the function signature.
Eating example
Here is an example:
function goingThroughTheEatingProcess(plenty, of, args, to, match, real, life) {
return iAmAsync()
.then(chew)
.then(swallow);
function chew(result) {
return carefullyChewThis(plenty, of, args, "water", "piece of tooth", result);
}
function swallow(wine) {
return nowIsTimeToSwallow(match, real, life, wine);
}
}
function iAmAsync() {
return Promise.resolve("mooooore");
}
function carefullyChewThis(plenty, of, args, and, some, more) {
return true;
}
function nowIsTimeToSwallow(match, real, life, bobool) {
}
Do not focus too much on the Promise.resolve(). It is just a quick way to create a resolved promise.
What I try to achieve by this is to have all the code I am running in a single location - just underneath the thens.
All the others functions with a more descriptive name are reusable.
The drawback with this technique is that it is defining a lot of functions.
But it is a necessary pain I am afraid in order to avoid having anonymous functions all over the place.
And what is the risk anyway: a stack overflow? (joke!)
Using arrays or objects as defined in other answers would work too. This one in a way is the answer proposed by Kevin Reid.
You can also use bind() or Promise.all().
Note that they will still require you to split your code.
using bind
If you want to keep your functions reusable but do not really need to keep what is inside the then very short, you can use bind().
function tata( amazingData ) {
return afterSomething( amazingData )
.then( afterSomethingElse.bind(null, amazingData) );
}
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( amazingData, processedData ) {
}
To keep it simple, bind() will prepend the list of args (except the first one) to the function when it is called.
using Promise.all
In your post you mentionned the use of spread(). I never used the framework you are using, but here is how you should be able to use it.
Some believe Promise.all() is the solution to all problems, so it deserves to be mentioned I guess.
function tata( amazingData ) {
return Promise.all( [ amazingData, afterSomething( amazingData ) ] )
.then( afterSomethingElse );
}
function afterSomething( amazingData ) {
return processAsync( amazingData );
}
function afterSomethingElse( args ) {
var amazingData = args[0];
var processedData = args[1];
}
You can pass data to Promise.all() - note the presence of the array - as long as promises, but make sure none of the promises fail otherwise it will stop processing.
And instead of defining new variables from the args argument, you should be able to use spread() instead of then() for all sort of awesome work.
Whatever you return from a promise will be wrapped into a promise to be unwrapped at the next .then()
stage.
It becomes interesting when you need to return one or more promise(s) alongside one or more synchronous value(s) such as;
Promise.resolve([Promise.resolve(1), Promise.resolve(2), 3, 4])
.then(([p1,p2,n1,n2]) => /* p1 and p2 are still promises */);
In these cases it would be essential to use Promise.all()
to get p1
and p2
promises unwrapped at the next .then()
stage such as
Promise.resolve(Promise.all([Promise.resolve(1), Promise.resolve(2), 3, 4]))
.then(([p1,p2,n1,n2]) => /* p1 is 1, p2 is 2, n1 is 3 and n2 is 4 */);