116

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 a 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.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
Oliver Salzburg
  • 21,652
  • 20
  • 93
  • 138
  • 2
    possible duplicate of [javascript promise not passing all arguments (using Q)](http://stackoverflow.com/q/17970420/1048572) and [How do I access previous promise results in a .then() chain?](http://stackoverflow.com/q/28250680/1048572) – Bergi Feb 24 '15 at 18:45
  • 1
    Possible duplicate of [Can promises have multiple arguments to onFulfilled?](https://stackoverflow.com/questions/22773920/can-promises-have-multiple-arguments-to-onfulfilled) – Hirurg103 Mar 15 '19 at 22:14
  • De-structuring Assignment in ES6 would help. check [here](https://stackoverflow.com/a/61136924/6270421) – Ravi Teja Oct 06 '20 at 11:37

9 Answers9

105

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.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 28
    What's wrong with resolving with an object that has multiple properties? Seems like a simple way to get multiple values out of a resolve. – jfriend00 Feb 24 '15 at 21:52
  • 6
    It's perfectly fine to do that – Benjamin Gruenbaum Feb 24 '15 at 21:53
  • You can also extend Promise to have `.spread()` like Bluebird showing in this related answer: http://stackoverflow.com/a/22776850/1624862 – Kevin Ghadyani Oct 28 '16 at 17:37
  • The behavior of Promise.all() seems to contradict this. `Promise.all([a, b, c]).then(function(x, y, z) {...})` works correctly in all modern Javascript engines with x, y, and z evaluating to the resolved values of a, b, and c. So it's more accurate to say that the language doesn't let you do it easily (or sanely) from user code (because you can return a Promise directly from a then clause, you can wrap your values in promises and then wrap those with Promise.all() to get the desired behavior, albeit in a convoluted manner). – Austin Hemmelgarn Jul 01 '19 at 00:05
  • 8
    @AustinHemmelgarn That is just false, `Promise.all` fulfills [with an array](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-performpromiseall). In `Promise.all([a,b]).then((a, b) =>` `b` is `undefined`. That's why you need to do `.then(([a, b]) =>` which is a destructuring assignment – Benjamin Gruenbaum Jul 01 '19 at 12:26
  • I would just concat them and split like the pros do – Ralph Dingus Sep 21 '20 at 20:07
51

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"
})
Alejandro Silva
  • 8,808
  • 1
  • 35
  • 29
  • 5
    It's called *destructuring*, not "deconstructor", and it's not an operator :-/ – Bergi Sep 04 '15 at 22:19
  • 3
    Btw, you can use destructuring right in the parameters: `function step2([server, data]) { …` - that way you also avoid to assign to implicit globals. And you really should use `return` or `Promise.resolve`, not the `new Promise` constructor in your examples. – Bergi Sep 04 '15 at 22:21
  • thans @Bergi for the recomendations! – Alejandro Silva Apr 04 '17 at 04:16
22

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
  });
}
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • 5
    It is ok to return a `then` inside another `then` ? It is not an _anti-pattern_ ? – robe007 Sep 17 '18 at 23:28
  • 1
    like @robe007 said, would't this be similar to 'callback hell'? here your nesting then blocks instead of callback functions, this would defeat the very purpose of having promises – some_groceries Mar 22 '20 at 13:33
7

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);
});
6

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
}
jemiloii
  • 24,594
  • 7
  • 54
  • 83
6

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 */);
Redu
  • 25,060
  • 6
  • 56
  • 76
3

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.

Community
  • 1
  • 1
gabriel
  • 176
  • 1
  • 9
0

You can check Observable represented by Rxjs, lets you return more than one value.

codelovesme
  • 3,049
  • 1
  • 16
  • 18
0

Simply return a tuple:

    async add(dto: TDto): Promise<TDto> {
    console.log(`${this.storeName}.add(${dto})`);
    return firebase.firestore().collection(this.dtoName)
      .withConverter<TDto>(this.converter)
      .add(dto)
      .then(d => [d.update(this.id, d.id), d.id] as [any, string])
      .then(x => this.get(x[1]));
  }
Neobonde
  • 85
  • 2
  • 7
swissmawi
  • 51
  • 5