153

I'm following the spec here and I'm not sure whether it allows onFulfilled to be called with multiple arguments. For example:

promise = new Promise(function(onFulfilled, onRejected){
    onFulfilled('arg1', 'arg2');
})

such that my code:

promise.then(function(arg1, arg2){
    // ....
});

would receive both arg1 and arg2?

I don't care about how any specific promises implementation does it, I wish to follow the w3c spec for promises closely.

badunk
  • 4,310
  • 5
  • 27
  • 47
  • As a hint, I found that using https://github.com/then/promise (which is a barebones implementation) shows that it actually does not provide the 2nd argument – badunk Mar 31 '14 at 23:21
  • 2
    You want to use Bluebird with .spread. - also, stop caring about the spec, the spec is all about _interop_ between implementations and is minimal by design. – Benjamin Gruenbaum Apr 01 '14 at 04:16

9 Answers9

153

I'm following the spec here and I'm not sure whether it allows onFulfilled to be called with multiple arguments.

Nope, just the first parameter will be treated as resolution value in the promise constructor. You can resolve with a composite value like an object or array.

I don't care about how any specific promises implementation does it, I wish to follow the w3c spec for promises closely.

That's where I believe you're wrong. The specification is designed to be minimal and is built for interoperating between promise libraries. The idea is to have a subset which DOM futures for example can reliably use and libraries can consume. Promise implementations do what you ask with .spread for a while now. For example:

Promise.try(function(){
    return ["Hello","World","!"];
}).spread(function(a,b,c){
    console.log(a,b+c); // "Hello World!";
});

With Bluebird. One solution if you want this functionality is to polyfill it.

if (!Promise.prototype.spread) {
    Promise.prototype.spread = function (fn) {
        return this.then(function (args) {
            return Promise.all(args); // wait for all
        }).then(function(args){
         //this is always undefined in A+ complaint, but just in case
            return fn.apply(this, args); 
        });
    };
}

This lets you do:

Promise.resolve(null).then(function(){
    return ["Hello","World","!"]; 
}).spread(function(a,b,c){
    console.log(a,b+c);    
});

With native promises at ease fiddle. Or use spread which is now (2018) commonplace in browsers:

Promise.resolve(["Hello","World","!"]).then(([a,b,c]) => {
  console.log(a,b+c);    
});

Or with await:

let [a, b, c] = await Promise.resolve(['hello', 'world', '!']);
Maen
  • 10,603
  • 3
  • 45
  • 71
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • 2
    Note that other libaries (like Q) also support `.spread` like Bluebird - the reason it's not in the spec is that keeping the spec minimal is _really a big deal_ in order to allow interop between code and libraries. – Benjamin Gruenbaum Apr 01 '14 at 04:33
  • Second note - you might want to call `Promise.all` on the array before applying the function rather than just `.then`ing it to handle some sugar libraries provide. It's not mandatory, but it's cute. – Benjamin Gruenbaum Apr 01 '14 at 04:41
  • 1
    Promies.all is mandatory with your implementation, although you could just change the implementation to `return Promise.all(args).then(function(args){return fn.apply(this, args);})` – Esailija Apr 01 '14 at 07:53
  • Yes, that's what The second comment was supposed to say :) In order to imitate what libraries do. – Benjamin Gruenbaum Apr 01 '14 at 09:14
  • 14
    `spread` is a stopgap. ES6 introduces destructuring and the rest/spread operator, which eliminate the need for `spread` outright. `.then(([a, b, c]) => {})` – Kris Kowal Apr 01 '14 at 23:54
  • I think you're assuming I'm consuming promises and thus the solution is that I can utilize a promise library to fix my problem. I'm attempting to build a library to determine whether I can resolve(arg1, arg2) and have consumers of the library be able to receive both arguments as part of the spec. – badunk Apr 02 '14 at 23:41
  • 3
    @KrisKowal Note that .spread() implicitly does .all() but the ES6 destructuring syntax doesn't -> http://bluebirdjs.com/docs/api/spread.html – Gomino Dec 06 '16 at 19:12
  • @BenjaminGruenbaum It is not the same to use: `Promise.all(["Hello","World","!"]).then(([a,b,c]) => { console.log(a,b+c); });` ??? – robe007 Sep 17 '18 at 20:29
  • @BenjaminGruenbaum Another point: If I do: `Promise.resolve([anAsyncFunction(),"World","!"]).then(([a,b,c]) => { console.log(a,b+c); });` this returns: `Promise { } 'World !'` – robe007 Sep 17 '18 at 23:08
72

You can use E6 destructuring:

Object destructuring:

promise = new Promise(function(onFulfilled, onRejected){
    onFulfilled({arg1: value1, arg2: value2});
})

promise.then(({arg1, arg2}) => {
    // ....
});

Array destructuring:

promise = new Promise(function(onFulfilled, onRejected){
    onFulfilled([value1, value2]);
})

promise.then(([arg1, arg2]) => {
    // ....
});
Wookiem
  • 1,640
  • 1
  • 14
  • 18
21

The fulfillment value of a promise parallels the return value of a function and the rejection reason of a promise parallels the thrown exception of a function. Functions cannot return multiple values so promises must not have more than 1 fulfillment value.

Esailija
  • 138,174
  • 23
  • 272
  • 326
4

As far as I can tell reading the ES6 Promise specification and the standard promise specification theres no clause preventing an implementation from handling this case - however its not implemented in the following libraries:

I assume the reason for them omiting multi arg resolves is to make changing order more succinct (i.e. as you can only return one value in a function it would make the control flow less intuitive) Example:

new Promise(function(resolve, reject) {
   return resolve(5, 4);
})
.then(function(x,y) {
   console.log(y);
   return x; //we can only return 1 value here so the next then will only have 1 argument
})
.then(function(x,y) {
    console.log(y);
});
megawac
  • 10,953
  • 5
  • 40
  • 61
  • 8
    Q does not support multi-value resolutions because promises serve as proxies for the result of a function call but can also proxy for remote objects. In both of these cases, an array is the only sensible representation of a compound value. With the addition of destructuring and “spread” arguments in ES6, the syntax gets really nice. The “spread” method is a stopgap. – Kris Kowal Apr 01 '14 at 23:51
  • Well, you could always `return Promise.of(x, y)` instead of a scalar value from the `then` callback. – Bergi Jul 26 '14 at 08:56
2

Here is a CoffeeScript solution.

I was looking for the same solution and found seomething very intersting from this answer: Rejecting promises with multiple arguments (like $http) in AngularJS

the answer of this guy Florian

promise = deferred.promise

promise.success = (fn) ->
  promise.then (data) ->
   fn(data.payload, data.status, {additional: 42})
  return promise

promise.error = (fn) ->
  promise.then null, (err) ->
    fn(err)
  return promise

return promise 

And to use it:

service.get().success (arg1, arg2, arg3) ->
    # => arg1 is data.payload, arg2 is data.status, arg3 is the additional object
service.get().error (err) ->
    # => err
SherylHohman
  • 16,580
  • 17
  • 88
  • 94
Val Entin
  • 953
  • 10
  • 18
  • Should `->` be `=>` ? – SherylHohman May 03 '20 at 15:45
  • 1
    @SherylHohman Back in days in 2015 this was written with CoffeeScript (https://coffeescript.org/#introduction) and not ES6 syntax. Simple arrow was simple functions and fat arrows are nearly the same as ES6 (I guess ES6 fat arrows have been more or less borrowed from CoffeScript). – Val Entin May 06 '20 at 13:28
  • @SherylHohman Feel free to edit the post in ECMA if you want. – Val Entin May 06 '20 at 13:29
  • Thanks for your response. I'll edit only to clarify that this is a coffee script solution. With that, your answer stands as is & may be useful for CoffeeScript code bases. Thanks for your offer to edit, however: 1) I am not familiar enough with CoffeeScript to risk editing/breaking your solution ;-). 2) Translating your code to modern JS should be considered a deviation from the "original intent of your Answer", thus should not pass an 'edit' Review. Rather, someone could post a new Answer, if so inclined, translating your code. Ideally, they'd link back to your answer as their inspiration :-) – SherylHohman May 06 '20 at 14:58
2

De-structuring Assignment in ES6 would help here.For Ex:

let [arg1, arg2] = new Promise((resolve, reject) => {
    resolve([argument1, argument2]);
});
Ravi Teja
  • 182
  • 1
  • 9
0

Since functions in Javascript can be called with any number of arguments, and the document doesn't place any restriction on the onFulfilled() method's arguments besides the below clause, I think that you can pass multiple arguments to the onFulfilled() method as long as the promise's value is the first argument.

2.2.2.1 it must be called after promise is fulfilled, with promise’s value as its first argument.

SherylHohman
  • 16,580
  • 17
  • 88
  • 94
Jazzepi
  • 5,259
  • 11
  • 55
  • 81
0

Great question, and great answer by Benjamin, Kris, et al - many thanks!

I'm using this in a project and have created a module based on Benjamin Gruenwald's code. It's available on npmjs:

npm i -S promise-spread

Then in your code, do

require('promise-spread');

If you're using a library such as any-promise

var Promise = require('any-promise');
require('promise-spread')(Promise);

Maybe others find this useful, too!

Community
  • 1
  • 1
AndreasPizsa
  • 1,736
  • 19
  • 26
-1

To quote the article below, ""then" takes two arguments, a callback for a success case, and another for the failure case. Both are optional, so you can add a callback for the success or failure case only."

I usually look to this page for any basic promise questions, let me know if I am wrong

http://www.html5rocks.com/en/tutorials/es6/promises/

Michael Voznesensky
  • 1,612
  • 12
  • 15
  • 1
    Thats incorrect, `new Promise` has the syntax `function(resolve, error)` while `then` has the syntax `.then(function(arg) { ` – megawac Mar 31 '14 at 23:47
  • 2
    @megawac it's actually correct just badly put - then accepts two (sometimes 3) arguments - it's just rather uncommon – Benjamin Gruenbaum Apr 01 '14 at 06:46
  • @BenjaminGruenbaum afaik its `.then(function(/*resolve args*/){/*resolve handler*/}, function(/*reject args*/){/*reject handler*/})` – megawac Apr 01 '14 at 12:37
  • 2
    Yes, if you read closely, that's what this answer is claiming - not very useful in the context of this question but not incorrect. – Benjamin Gruenbaum Apr 01 '14 at 13:17