132

I'm using bluebird and I see two ways to resolve synchronous functions into a Promise, but I don't get the differences between both ways. It looks like the stacktrace is a little bit different, so they aren't just an alias, right?

So what is the preferred way?

Way A

function someFunction(someObject) {
  return new Promise(function(resolve) {
    someObject.resolved = true;
    resolve(someObject);
  });
}

Way B

function someFunction(someObject) {
  someObject.resolved = true;
  return Promise.resolve(someObject);
}
Pipo
  • 5,623
  • 7
  • 36
  • 46

2 Answers2

109

Contrary to both answers in the comments - there is a difference.

While

Promise.resolve(x);

is basically the same as

new Promise(function(r){ r(x); });

there is a subtlety.

Promise returning functions should generally have the guarantee that they should not throw synchronously since they might throw asynchronously. In order to prevent unexpected results and race conditions - throws are usually converted to returned rejections.

With this in mind - when the spec was created the promise constructor is throw safe.

What if someObject is undefined?

  • Way A returns a rejected promise.
  • Way B throws synchronously.

Bluebird saw this, and Petka added Promise.method to address this issue so you can keep using return values. So the correct and easiest way to write this in Bluebird is actually neither - it is:

var someFunction = Promise.method(function someFunction(someObject){
    someObject.resolved = true;
    return someObject;
});

Promise.method will convert throws to rejects and returns to resolves for you. It is the most throw safe way to do this and it assimilatesthenables through return values so it'd work even if someObject is in fact a promise itself.

In general, Promise.resolve is used for casting objects and foreign promises (thenables) to promises. That's its use case.

Community
  • 1
  • 1
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • "Promise returning functions should generally have the guarantee that they should not throw synchronously since they might throw asynchronously". Could you expand on why functions should be either synchronous or asynchronous but not both? Currently I enjoy Promise.resolve(), Would you go so far as to say using `Promise.resolve()` is an anti-pattern? – Ashley Coolman Jan 28 '16 at 12:50
  • 2
    @AshleyCoolman see http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony - a method that _sometimes_ behaves asynchronously should _always_ do so for consistency. – Benjamin Gruenbaum Jan 28 '16 at 14:05
  • Does `Promise.resolve()` create a new instance of `Promise` in the same way as using `new`? If not, `return Promise.resolve(yourCode)` would be faster and avoid synchronous throws. – Steven Vachon Feb 12 '16 at 03:30
  • 1
    I feel bad, I use "Promise.resolve().then(function(){ /*case that can throw an error*/}).then..." to be sure that the error becomes a rejected promise... I'll look more into the "Promise.method" – Polopollo Aug 24 '16 at 18:32
  • 1
    @Polopollo or `Promise.coroutine` which is even more useful. – Benjamin Gruenbaum Aug 24 '16 at 19:51
34

There is another difference not mentioned by the above answers or comments:

If someObject is a Promise, new Promise(resolve) would cost two additional tick.


Compare two following code snippet:

const p = new Promise(resovle => setTimeout(resovle));

new Promise(resolve => resolve(p)).then(() => {
  console.log("tick 3");
});

p.then(() => {
  console.log("tick 1");
}).then(() => {
  console.log("tick 2");
});

const p = new Promise(resolve => setTimeout(resolve));

Promise.resolve(p).then(() => {
  console.log("tick 3");
});

p.then(() => {
  console.log("tick 1");
}).then(() => {
  console.log("tick 2");
});

The second snippet would print 'tick 3' firstly. Why?

  • If the value is a promise, Promise.resolve(value) would return value exactly. Promise.resolve(value) === value would be true. see MDN

  • But new Promise(resolve => resolve(value)) would return a new promise which has locked in to follow the value promise. It needs an extra one tick to make the 'locking-in'.

      // something like:
      addToMicroTaskQueue(() => {
        p.then(() => {
          /* resolve newly promise */
        })
          // all subsequent .then on newly promise go on from here
          .then(() => {
            console.log("tick 3");
          });
      });
    

    The tick 1 .then call would run first.


References:
Abraham
  • 8,525
  • 5
  • 47
  • 53
edvard chen
  • 2,320
  • 1
  • 12
  • 10