20
loading...
This website collects cookies to deliver better user experience
Promise.resolve()
, if it returns an already fulfilled promise, why isn't it named something like Promise.fulfill()
? Similarly, remember the 1st argument of the executor function? Although we can name it anything we want, it's standard to use the name resolve()
for it. But again why is it not named fulfill()
since all it does is change the state of the promise to fulfilled
. Where did this word resolve come from?pending
, fulfilled
and rejected
. But it also has certain fates associated with it. These fates are resolved and unresolved and this is how the word resolve comes into play. So what decides whether a promise is resolved or unresolved? Let's find out.then()
handlers attached to this promise only consumed the response from the original promise and returned values like objects, strings, numbers or undefined
. The promise returned from then()
was fulfilled based on these values returned by its handlers.fetch("https://api.github.com/users/saurabh-misra/repos")
// returns an object
.then( response => response.json() )
// returns a string
.then( repos => repos[2].name )
// returns undefined
.then( console.log )
.catch( reason => console.error( reason ) );
/*
pomodoro-timer
*/
then()
returns an object and the returned promise is fulfilled with this object. The second then()
returns a string and the returned promise is fulfilled with this string.then()
handler instead of a simple string or a number? Does the returned promise get fulfilled with this promise? // fetch all repos
fetch("https://api.github.com/users/saurabh-misra/repos")
.then( response => response.json() )
// return the github URL of the 3rd repo in the list
.then( repos => repos[2].url )
// fetch details for this repo
.then( repoUrl => fetch(repoUrl) )
.then( response => response.json() )
.then( repoInfo => {
console.log("Name: ", repoInfo.name);
console.log("Description: ", repoInfo.description);
})
.catch( error => console.log("Error: ", error) );
/*
Name: pomodoro-timer
Description: A simple pomodoro timer web app
that helps you focus on your work.
*/
fetch()
call returns a list of all github repos for the particular github user. But instead of displaying the repo name, we choose a specific repo from this list and make a second fetch()
call using the repo url to extract detailed information about that repo like repo name and description.fetch()
calls.var reposUrl = "https://api.github.com/users/saurabh-misra/repos";
// fetch all repos
var promiseFetchRepos = fetch(reposUrl)
.then( response => response.json() )
// return the github URL of the 3rd repo in the list
.then( repos => repos[2].url );
// fetch details for the 3rd repo
var promiseFetchDetails = promiseFetchRepos
.then( repoUrl => {
var promiseSecondFetch = fetch(repoUrl);
return promiseSecondFetch;
});
promiseFetchDetails
.then( response => response.json() )
.then( repoInfo => {
console.log("Name: ", repoInfo.name);
console.log("Description: ", repoInfo.description);
})
.catch( error => console.log("Error: ", error) );
/*
Name: pomodoro-timer
Description: A simple pomodoro timer web app
that helps you focus on your work.
*/
promiseSecondfetch
which is returned by the second fetch()
call. How does this affect promiseFetchDetails
? If a string or a number was returned, promiseFetchDetails
would have been fulfilled with that value. But in this case does it get fulfilled with the value as promiseSecondfetch
? Nope.promiseFetchDetails
will follow promiseSecondfetch
. But what does that mean?promiseFetchDetails
will surrender its own ability to fulfil or reject itself and instead lock on to the state of promiseSecondfetch
. If promiseSecondfetch
is pending, promiseFetchDetails
will be pending. If promiseSecondfetch
gets fulfilled with some value, promiseFetchDetails
will also get fulfilled with the same value. If promiseSecondfetch
gets rejected with some reason, promiseFetchDetails
will also get rejected with the same reason. This behaviour is what makes promiseFetchDetails
a resolved promise.promiseFetchDetails
from the previous example. When promiseFetchDetails
is initialized by the then()
call, it is initially in the pending state. At this point, its fate is unresolved as a return/error from any one of the handlers of its own then()
call can resolve or reject it. then()
is invoked, it returns a promise i.e. promiseSecondfetch
. At this point, promiseFetchDetails
surrenders its ability to resolve or reject on its own and starts following promiseSecondfetch
. So both, promiseFetchDetails
and promiseSecondfetch
are in the pending state but now promiseFetchDetails
's fate has transitioned to become a resolved promise. When promiseSecondfetch
gets fulfilled a little while later, promiseFetchDetails
also gets fulfilled with the same value. It still remains a resolved promise but now in the fulfilled
state.Promise.resolve()
instead of a simple string or number or in general a non-promise value.var promise1 = Promise.resolve( 1 );
var promise2 = Promise.resolve( promise1 );
console.log( promise2 );
// Promise { <state>: "fulfilled", <value>: 1 }
Promise.resolve()
is passed a promise object, promise1
, which is why promise2
begins following promise1
and gets fulfilled with the same value as promise1
.resolve()
in the executor function instead of a non-promise value.var promise1 = Promise.resolve( 1 );
var promise2 = new Promise( resolve => {
// async operation goes here...
resolve( promise1 );
});
promise2.then( console.log );
// 1
resolve()
call is passed a promise object, promise1
which results in promise2
following promise1
and getting fulfilled with the same value as promise1
.then()
handlers, Promise.resolve()
and resolve()
can all unwrap a promise object.Promise.reject()
? Answer is...it doesn't. Yes that's right, Promise.reject()
cannot unwrap promises which means the promise returned by Promise.reject()
can never follow another promise.var promise1 = Promise.resolve( 1 );
var promise2 = Promise.reject( promise1 );
console.log( promise2 );
/*
Promise {
<state>: "rejected",
<reason>: Promise {
<state>: "fulfilled",
<value>: 1
}
}
Uncaught (in promise) Promise { <state>: "fulfilled", <value>: 1 }
*/
Promise.reject()
does not unwrap promise1
. promise2
does not follow promise1
and does not get resolved or rejected with a value/reason of 1. Instead it rejects with the reason as the entire promise1
object.Promise.reject()
represents a failure situation where an error should be thrown. If Promise.reject()
could unwrap promise1
in the example above, promise2
would get fulfilled with the value 1 which would silence the error that Promise.reject()
was trying to throw in the first place.reject()
call in the executor function.var promise1 = Promise.resolve( 1 );
var promise2 = new Promise( (resolve, reject) => {
// async operation goes here...
reject( promise1 );
});
promise2
.catch( reason => console.log("Rejection reason: ", reason) );
/*
Rejection reason:
Promise { <state>: "fulfilled", <value>: 1 }
*/
reject()
function does not unwrap promise1
. It instead uses it as the rejection reason which is what is logged later in the catch()
handler.var promise1 = Promise.resolve( 1 );
var promise2 = Promise.resolve( promise1 );
var promise3 = Promise.resolve( promise2 );
var promise4 = Promise.resolve( promise3 );
console.log( promise4 );
// Promise { <state>: "fulfilled", <value>: 1 }
promise4
is the first promise that follows the 2nd one i.e. promise3
and so on till promise1
which resolves to 1. Promise.reject()
call in their somewhere?var promise1 = Promise.resolve( 1 );
var promise2 = Promise.resolve( promise1 );
var promise3 = Promise.reject( promise2 );
var promise4 = Promise.resolve( promise3 );
var promise5 = Promise.resolve( promise4 );
console.log( promise5 );
/*
Promise {
<state>: "rejected",
<reason>: Promise { <state>: "fulfilled", <value>: 1 }
}
Uncaught (in promise)
Promise { <state>: "fulfilled", <value>: 1 }
*/
promise2
follows promise1
and gets fulfilled with a value of 1. Promise.reject()
will be unable to unwrap promise2
. So promise3
will reject with the entire promise2
object as the error reason. promise4
will follow promise3
and promise5
will in turn follow promise4
and both will attain the rejected
state with the same reason as promise3
.then()
handlers return a promise but we have not talked about the behaviour when this happens inside catch()
and finally()
handlers. then()
function with undefined
as the fulfilled handler. So its behaviour is pretty much the same as then()
which we have already seen but let's consider an example anyway.var promise1 = Promise.resolve( 1 );
Promise.reject( "oh no!" )
.catch( reason => promise1 )
.then( console.log );
// 1
catch()
follows promise1
and gets fulfilled with the value of 1. This value is then passed to then()
's fulfilled handler which logs it to the console.finally()
behaves differently than then()
and catch()
in this case. In Part III of this series, we discussed that the finally()
handler is meant to do cleanup and not really supposed to return anything meaningful. It does return a promise but that is simply for the purpose of forming a promise chain. So its returned promise already follows the original promise on which it was invoked. Returning anything from the finally()
handler has no effect on this behaviour. Let's see this in action.var promise1 = Promise.resolve( 1 );
Promise.resolve( 2 )
.finally( reason => promise1 )
.then( console.log );
// 2
finally()
handler returns promise1
but that is ignored. The returned promise from finally()
is already locked on to the returned promise of the second Promise.resolve()
which is fulfilled with the value 2. So the returned promise from finally()
also gets fulfilled with the value 2 and not 1. then()
and catch()
handlersPromise.resolve()
resolve()
in the executor functiondeferreds
. The name and the implementation might differ from library to library but the intention is the same, making asynchronous code behave like synchronous code. then()
method. // customPromise defines a `then()` method,
// so that makes it a thenable.
var customPromise = {
then: function( onFulfilled, onRejected ) {
// a very simple 'then' method implementation.
// promise spec requires onFulfilled to be called asynchronously.
setTimeout( () => onFulfilled( 1 ), 1000);
}
};
then()
method whereas so far we have been calling it on a promise object. then()
method. Since it follows the rules laid out by the spec, it will work seamlessly with native JS promises.var thenable = {
then: function( onFulfilled, onRejected ) {
setTimeout( () => onFulfilled( 1 ), 1000);
}
};
Promise.resolve()
.then( () => customPromise )
.then( console.log );
// 1
then()
fulfilled handler, it checks whether this object can be unwrapped. Since this is a thenable and defines a then()
method and follows the Promise specification, JS will be able to unwrap it. then()
method of the thenable as an executor function. Just like its native counterpart, JS will pass in 2 arguments(like resolve()
and reject()
) to this custom then()
method and will wait for either of them to be called. This means that the thenable will take on the pending state initially. Since the onFulfilled()
handler is called after 1 second, the thenable will be considered fulfilled
with whatever value the handler returns, in this case, 1.then()
handler is able to follow this thenable just like it would follow a native promise object. Line 9 will log the fulfilled value i.e "1" which confirms that the returned promise from the first then()
has successfully been resolved with the thenable. onRejected
handler of the custom then()
function is invoked. You can probably guess by now that it will reject the returned promise with the reason returned from the handler and you'd be right.var customPromise = {
then: function( onFulfilled, onRejected ) {
setTimeout( () => onRejected( "oh no!" ), 1000);
}
};
Promise.resolve()
.then( () => customPromise )
.catch( console.log );
// oh no!
then()
and catch()
handlers, Promise.resolve()
and resolve()
in the executor function.Promise.reject()
and reject()
in the executor function cannot unwrap promises/thenables. Also finally()
ignores any promise returned from within its handler.