28

I understand Promises to exist in one of three states: A Promise can either be pending (unresolved), fulfilled (resolved successfully) or rejected (resolved unsuccessfully).

Reading through the A+ Promise Spec and MDN's documentation, I am confused that they both acknowledge the fulfilled and rejected states but in the definition of the Promise constructor they specify two callbacks: resolve and reject. It seems we're using these two terms interchangeably; they are not.

Does not imply success:

re·solve /rəˈzälv/ verb
1. settle or find a solution to (a problem, dispute, or contentious matter).

Does imply success:

ful·fill /fo͝olˈfil/ verb
1. bring to completion or reality; achieve or realize (something desired, promised, or predicted).
2. carry out (a task, duty, or role) as required, pledged, or expected.

Why are we using resolve here when we're actually fulfilling the Promise? Is there an instance in which the value we pass to resolve might result in the Promise being rejected?

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
rrowland
  • 2,734
  • 2
  • 17
  • 32
  • See http://stackoverflow.com/questions/35042068/why-is-onrejected-not-called-following-promise-all-where-promise-reject-incl – guest271314 Feb 14 '16 at 22:01
  • 1
    This link doesn't seem relevant. Can you elaborate? – rrowland Feb 14 '16 at 22:04
  • 1
    _"It occurred to me that perhaps if I pass an error to the resolve callback, it would reject the Promise."_ The `Promise` was resolved with `Error` as value , the `Promise` was not rejected . – guest271314 Feb 14 '16 at 22:05
  • Right. I'm not asking why that doesn't work. I was using that example to illustrate that the `resolve` function is actually performing the `fulfill` function as it simply passes along the value, aka fulfilling the Promise. – rrowland Feb 14 '16 at 22:10
  • Yes, not certain what Question is ? – guest271314 Feb 14 '16 at 22:11
  • It's right at the bottom of the post. – rrowland Feb 14 '16 at 22:12
  • 1
    I use `new Promise(function(fulfill, reject) {...})` sometimes ... I also use `new Promise(function(ok, bad) {...})` - and various other weird names, they're just argument names after all and I don't let semantics slow down my coding – Jaromanda X Feb 14 '16 at 22:26
  • possible duplicate of [What is the correct terminology for javascript promises](http://stackoverflow.com/a/29269515/1048572) – Bergi May 10 '16 at 17:54
  • Great question, the terminology is something that annoys me now and then, after I've been away from promises for awhile. As you pointed out, resolved should mean "no longer pending", a promise can resolve with either success or fail - BUT MOST OFTEN resolve is used interchangeably with fulfilled (success). – joedotnot Jan 27 '21 at 11:52

4 Answers4

26

Indeed, the resolve callback does not imply that the promise will be fulfilled.

The terms fulfilled, rejected, pending, settled, resolved and locked-in are defined in the EcmaScript2015 specs, 25.4 Promise Objects:

Any Promise object is in one of three mutually exclusive states: fulfilled, rejected, and pending:

  • A promise p is fulfilled if p.then(f, r) will immediately enqueue a Job to call the function f.

  • A promise p is rejected if p.then(f, r) will immediately enqueue a Job to call the function r.

  • A promise is pending if it is neither fulfilled nor rejected.

A promise is said to be settled if it is not pending, i.e. if it is either fulfilled or rejected.

A promise is resolved if it is settled or if it has been “locked in” to match the state of another promise. Attempting to resolve or reject a resolved promise has no effect. A promise is unresolved if it is not resolved. An unresolved promise is always in the pending state. A resolved promise may be pending, fulfilled or rejected.

A short overview, where I will use the term "autonomous" as the opposite of "locked in". They are the two possible values for a promise's dependency situation:

action dependency state resolved? settled?
new Promise((resolve, reject) => ...) autonomous pending no no
...resolve(thenable) locked-in pending* yes no
...resolve(other) autonomous fulfilled yes yes
...reject(any) autonomous rejected yes yes

* The thenable is now in control over the future state of our promise object.

The above quote mentions that a promise is locked-in to match the state "of another promise", but more precisely that "other promise" could also be a non-promise "thenable" as can be seen in the steps 11 and 12 of the process description in 25.4.1.3.2

  1. If IsCallable(thenAction) is false, then
          a. Return FulfillPromise(promise, resolution).
  2. Perform EnqueueJob ("PromiseJobs", PromiseResolveThenableJob, «‍promise, resolution, thenAction»)

A demo of resolve being called with a thenable, which in turn triggers a rejection:

const thenable = { // Could be a promise object, but does not have to be
    then(success, fail) {
        setTimeout(() => fail("gotcha!"), 1000);
    }
}

const p = new Promise((resolve, reject) => {
    console.log("1. The promise is created as pending");
    setTimeout(() => {
        resolve(thenable);
        console.log("2. It's resolved with a thenable; it's not yet settled");
    }, 1000);
});

p.catch(err => 
   console.log(`3. It's settled as rejected with error message "${err}"`)
);
trincot
  • 317,000
  • 35
  • 244
  • 286
10

We can resolve a promise with another promise.

To answer your second question first: Yes, there is an instance in which the value we pass to resolve might result in the Promise being rejected, and that is if we pass it a rejected promise, e.g. Promise.reject().

To answer your first question of isn't resolve and fulfill the same: Consider the case where the value we pass to resolve is a pending promise. In this case our own promise will not settle immediately as a result:

a().then(() => new Promise(setTimeout)).catch(e => console.error(e));

In this case we say a promise is "resolved to" another promise, and it remains pending.

This is all happening behind our backs inside then, so it might be easier to look at a vintage case where a does not support promises (takes callbacks), and we don't flatten things correctly:

// Old times and unflattened for exposition:
new Promise((resolve, reject) => a(function(result) {
  resolve(new Promise(setTimeout));
}, reject))
.then(() => console.log("after setTimeout"))
.catch(e => console.error(e));

Here we see more clearly that resolve is called with another promise. Importantly, the resolved promise does not fulfill and fire the "after setTimeout" message until the second promise resolves (with a non-promise undefined value from setTimeout), at which point both promises become fulfilled (in other words: these two promises just formed a resolve chain).

This is key to understanding that resolved is different from fulfilled or even settled (fulfilled or rejected, not pending).

From States and Fates:

  • states: fulfilled, rejected, pending.
  • fates: resolved, unresolved.

The fate refers to whether the fate of a single promise has been reached, and does not correspond directly to any state transition, because of resolve chains.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
jib
  • 40,579
  • 17
  • 100
  • 158
  • What if `Promise.reject()` is passed to `resolve`? Won't that cause this Promise to reject as well? – rrowland Feb 16 '16 at 16:07
  • To answer my own question: `resolve(Promise.reject())` will resolve the Promise's fate to another Promise which is rejected, resulting in this Promise being rejected. I think the important and misleading factor here is that `resolve` is referring to a fate transition and `reject` is referring to a state transition. Seems to me only `resolve` is necessary and `reject` is a confusing sugar function for `resolve(Promise.reject())`. – rrowland Feb 16 '16 at 16:24
  • It can be immediately rejected, the spec doesn't say anywhere that rejection must happen asynchronously although `then` handlers are always called asynchronously. – MinusFour Feb 16 '16 at 16:46
  • @rrowland, `reject` callbacks aren't actually `resolve(Promise.reject())`. `Promise.reject()` creates a **new promise** that is rejected. While `reject` just rejects the constructed promise. Of course, they would both end up rejecting the constructed promise, it's just unnecessary to create a new promise just so it can adopt its rejection. – MinusFour Feb 16 '16 at 16:52
  • @MinusFour Then would it not be fair to say that `resolve(Promise.reject())` actually fulfills this Promise with another Promise, adding the new Promise to the chain, which is then rejected? – rrowland Feb 16 '16 at 16:55
  • You'd say that the Promise A resolves to the same state/value as Promise B. Promise A can be rejected or fulfilled, depending on the sate of Promise B. Like I said in my answer, `fulfill` can be somewhat tricky because you could confuse it with the completion of the resolution process, which is not. – MinusFour Feb 16 '16 at 17:04
  • @MinusFour thanks, you're right. `var p = Promise.resolve(Promise.reject(new Error("hi")))` does in fact show `p` as immediately `rejected` when observed in the JS debugger in both Firefox and Chrome. I'll update my answer. – jib Feb 16 '16 at 17:09
  • So `resolve` doesn't immediately complete the resolution process but `reject` does. – rrowland Feb 16 '16 at 18:09
  • `resolve` can complete immediately, sometime later or never (e.g. unresolvable promises). `reject` on the other hand skips `resolve` and rejects the promise immediately. Behavior isn't specified though (at least not by this spec), so it may vary. – MinusFour Feb 16 '16 at 18:34
  • It seems well-behaved in implementations. `var p = Promise.resolve(Promise.resolve("hi"))` shows `p` as immediately `fulfilled` when immediately observed in the JS debugger in both Firefox and Chrome. And `var q = Promise.resolve(new Promise(setTimeout))` shows as `pending`. WFM. – jib Feb 16 '16 at 18:52
2

A resolved Promise having a value of an Error does not automatically convert the Promise to rejected Promise

var p = Promise.resolve(new Error("rejected"));
p.then(function(data) {
  console.log(data, p)
})

See also States and fates


If you could include information on how resolve might be used to reject the Promise rather than fulfill it, I would consider this a complete answer.

Not certain about reason for or expected result of using resolve to reject a Promise ? Though simplest approach to achieve this would be to pass reject as a parameter when resolve called Promise.resolve(Promise.reject(/* Error here */))

var _reject = function(err) {
  return Promise.reject(err)
}

var resolver = function(resolve, reject) {  
    return resolve(_reject(new Error("reject within resolve")))
}

var p = new Promise(resolver);

p.then(function(data) {
  console.log("resolved", data)
},function(data) {
  console.log("rejected:", data, "promise:", p)
})

If expected result is to catch errors or a rejected Promise passed to resolve where handlers may be attached, while maintaining the "resolved" PromiseStatus , could use unhandledrejection event , Promise.reject directly or at chrome / chromium 49+ could use PromiseRejectionEvent

window.addEventListener("unhandledrejection", function(event) {
  // handle unhandled rejected `Promise`
  console.log("unhandledrejection:", event.reason, event.promise);
});

Promise.resolve(new PromiseRejectionEvent("Error", {
    // unhandled `rejected` `Promise` 
    promise: Promise.reject(new Error("custom rejection")),
    reason: "custom rejection"
  }))
  .then(function(data) {
    // `PromiseRejectionEvent` contains a `rejected`
    // `Promise` , which triggers `"unhandledrejection"` event
    // to handle rejected `Promise` here, resolve `.promise` 
    // object of `PromiseRejectionEvent`
    console.log("resolved:", data);
  }, function(err) {
    console.log("rejected", err)
})

could also throw an Error within Promise constructor without using resolve or reject , which should be handled by onRejected or catch

new Promise(function(resolve, reject) {
  throw new Error("reject within Promise constructor")
})
// catch here handles `Error` from `Promise` constructor
// will be `resolved` at `.then()` if `Error` not `throw`n to `.then()` 
// .catch(function(e) {
//  console.log("caught error:", e);
  /* return e : pass `e` to `.then()` as `resolved` `Promise` */
  /* throw e : pass `e` to `.then()` as `rejected` `Promise`  */
//})

.then(function(data) {
  console.log("resolved:", data)
}, function(err) {
  console.log("rejected:", err);
  throw err
})
.catch(function(e) {
  console.log("caught error:", e);
  /* return e : pass `e` to `.then()` as `resolved` `Promise` */
  /* throw e : pass `e` to `.then()` as `rejected` `Promise`  */
})

Explanation: There are a number of approaches to handling both resolved and rejected Promise objects , depending on application and expected results.

guest271314
  • 1
  • 15
  • 104
  • 177
  • As made clear in comments above, this is addressing a question I did not ask. I was using this example to illustrate that the `resolve` function is actually performing the `fulfill` function as it simply passes along the value, aka fulfilling the Promise – rrowland Feb 14 '16 at 22:18
  • @rrowland Is _"Is there a case to be made for the newer terms?"_ the actual Question ? – guest271314 Feb 14 '16 at 22:19
  • I realize my original question was vague and the examples I used may have been misleading. I have updated my question. I have already discovered the answer on my own but your answer seems on the right track. If you could include information on how `resolve` might be used to reject the Promise rather than fulfill it, I would consider this a complete answer. – rrowland Feb 15 '16 at 22:16
  • @rrowland: If you have already discovered the answer on your own, could you please post it as an answer to this question? I think I'm confused in a very similar way to how you were, and I would love to see how you resolved (cough cough) this conundrum. – LarsH Jan 22 '17 at 23:08
2

I think it's common to say that the Promise has resolved or settled. The resolution of the promise is the process in which the promise moves from the pending state and acquires a value associated to said state. So, if a promise is either fulfilled or rejected it will be a resolved promise (as it's resolution process has ended). If a promise enters the resolution process and never transitions into any other state it is said that the promise is unresolved (the resolution process never ended).

Regarding the other terms rejected or fulfilled, they are the other two states in which a pending promise can transition from. reject is pretty obvious IMO, it handles cases in which failure is supposed to happen. Now I do agree that fulfill can be somewhat ambiguous because it could simply mean that the promise has completed successfully (as in being resolved). It isn't supposed to describe the resolution process but the success (or the absence of error) of the task at hand.

The resolution process (to resolve a promise) can be observed in the A+ spec.

Edit.

The reason why people usually use resolve as the first argument name it's because the callback passed as the first argument invokes the resolution process. It doesn't fulfill the promise (the promise can still be rejected), it just starts resolving the promise. The reject mechanism isn't specified in the spec, it actually kind of short circuits the resolution process so that the promise is settled with reject (not actually resolved you see).

Here are some examples where p is rejected by using resolve:

This is point 2.3.1.

var p = new Promise(resolve => setTimeout(() => resolve(p), 0));

This is point 2.3.2.3.

var p = Promise.resolve(Promise.reject('reason'));

This is point 2.3.3.2.

var thenable = { get then() { throw new Error(); } }
var p = Promise.resolve(thenable);

This is point 2.3.3.3.3

var thenable = {
    then: function(resolvePromise, rejectPromise){
        rejectPromise(new Error());
    }
}
var p = Promise.resolve(thenable);

This is point 2.3.3.4.2

var thenable = {
    then: function(){
        throw new Error();
    }
}
var p = Promise.resolve(thenable);

I used Promise.resolve here instead of the first argument of the function passed down to the Promise constructor, but they should be the same. Many times the resolve function passed down to the constructor is:

var p = this;
var cb = function(x){
    resolve(p, x);
}

You can of course write down these tests as:

var p = new Promise(function(resolve){
    resolve(thenable);
});
MinusFour
  • 13,913
  • 3
  • 30
  • 39