23
loading...
This website collects cookies to deliver better user experience
Promise()
constructor. We also saw the various states a promise can be in as well as how to make the promise transition from pending
to either fulfilled
or rejected
states. pending
to fulfilled
and your failure scenario code runs when it transitions from pending
to rejected
.Promise
class. We'll focus on the instance methods in this article and tackle static methods in the next one.then()
, catch()
and finally()
. Let's look at them one by one.then()
that lets you associate handlers to execute code when the promise is fulfilled
or rejected
. It accepts two functions as arguments. The first one acts as the handler for the fulfilled
state and the other one for the rejected
state. var promise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "I am now fulfilled😇" );
}, 1000);
});
var handleFulfilled = value => { console.log( value ); };
promise.then( handleFulfilled );
// I am now fulfilled😇
then()
will hook the handleFulfilled()
handler to the promise object so that it gets invoked if the promise is fulfilled. Not only that but the handleFulfilled()
function will also receive the value
the promise is fulfilled with(the value we pass to the resolve()
call) as an input. After the 1 second timeout, the promise transitions to the fulfilled
state and handleFulfilled()
gets called and logs the value we passed to the resolve()
function onto the console.var promise = new Promise( (resolve, reject) => {
setTimeout(() => {
reject( "something went wrong🤦♂️" );
}, 1000);
});
var handleFulfilled = value => { console.log( value ); };
var handleRejected = reason => { console.log( reason ); };
promise.then( handleFulfilled, handleRejected );
// something went wrong🤦♂️
handleRejected()
function works like an error handler and catches the error thrown by reject()
. The error reason we called reject()
with, is passed to the handler as an input. In this case, after the 1 second timeout, the promise gets rejected and our handler is invoked. It simply logs the reason to the console and suppresses the error.then()
method returns a new promise object. When the original promise gets settled and either of the two handlers are invoked, the eventual state of this returned promise depends upon what happens inside the handleFulfilled()
and handleRejected()
handlers. resolve()
and reject()
were responsible for changing the state of the original promise, handleFulfilled()
and handleRejected()
will be responsible for changing the state of the promise returned by then()
. undefined
. If either of these handlers throw an error, the returned promise will get rejected.var origPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "original promise is fulfilled😇" );
}, 1000);
});
var handleFulfilled = value => {
console.log( value );
return "returned promise is also fulfilled😇😇";
};
var returnedPromise = origPromise.then( handleFulfilled );
// log the returned promise in the console
// before the async op has completed.
console.log( "Returned Promise before:", returnedPromise );
// log the returned promise in the console
// after the async op has completed.
setTimeout(() => {
console.log( "Returned Promise after:", returnedPromise );
}, 2000);
/*
OUTPUT
Returned Promise before: Promise { <state>: "pending" }
original promise is fulfilled😇
Returned Promise after: Promise {
<state>: "fulfilled",
<value>: "returned promise is also fulfilled😇😇"
}
*/
then()
method returns a new promise i.e returnedPromise
. It initially remains in the pending
state. When origPromise
resolves after the 1 second timeout, the handleFulfilled()
handler is invoked which returns a string. Since it returns a value, returnedPromise
gets fulfilled with this value or string. We have a second setTimeout()
on line 21 to log returnedPromise
after 2 seconds i.e well after the 1 second timeout and after both promises have resolved.handleFulfilled()
handler, returnedPromise
will be rejected with the error reason returned from handleFulfilled()
. If no reason is specified, it'll be rejected with undefined
.var origPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "original promise is fulfilled😇" );
}, 1000);
});
var handleFulfilled = value => {
console.log( value );
throw("Something went wrong🤦♂️");
};
var returnedPromise = origPromise.then( handleFulfilled );
// log the returned promise in the console
// before the async op has completed.
console.log( "Returned Promise before:", returnedPromise );
// log the returned promise in the console
// after the async op has completed.
setTimeout(() => {
console.log( "Returned Promise after:", returnedPromise );
}, 2000);
/*
OUTPUT
Returned Promise before: Promise { <state>: "pending" }
original promise is fulfilled😇
Uncaught (in promise) Something went wrong🤦♂️
Returned Promise after: Promise {
<state>: "rejected",
<reason>: "Something went wrong🤦♂️"
}
*/
handleRejected()
handler. If it returns a value, then returnedPromise
will be fulfilled with that value. If an error occurs, returnedPromise
will be rejected with the error reason.then()
call. Yes, that's right! Both the input arguments to then()
are optional. If we skip them, the returned promise will just mimic the original promise.var origPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "original promise is fulfilled😇" );
}, 1000);
});
var returnedPromise = origPromise.then();
// log the returned promise in the console
// before the async op has completed.
console.log( "Returned Promise before:", returnedPromise );
// log the returned promise in the console
// after the async op has completed.
setTimeout(() => {
console.log( "Returned Promise after:", returnedPromise );
}, 2000);
/*
OUTPUT
Returned Promise before: Promise { <state>: "pending" }
Returned Promise after: Promise {
<state>: "fulfilled",
<value>: "original promise is fulfilled😇"
}
*/
then()
method. This is why when origPromise
gets fulfilled with a value, returnedPromise
gets fulfilled with the same value.origPromise
gets rejected with a reason, returnedPromise
will get rejected with the same reason.then()
returns a new promise is a powerful tool in the promise arsenal. We can attach then()
methods one after the other forming a chain of then()
methods. Each then()
method's handler is executed in the order in which it was attached in the chain. The value returned by a then()
method's handler is passed on to the handleFulfilled
handler of the next then()
method. An error thrown by a then()
method's handler is caught by the first subsequent then()
method further down in the chain that has a rejected handler defined. If no rejected handler is defined by any of the subsequent then()
methods, then an uncaught exception will be thrown.var thingsToBuyPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "Cheese🧀" );
}, 1000);
});
thingsToBuyPromise
// 1st
.then( value => {
console.log( "1. " + value ); // 1. Cheese🧀
return "Milk🥛";
})
// 2nd
.then( value => {
console.log( "2. " + value ); // 2. Milk🥛
return ("Butter🧈");
})
// 3rd
.then( value => {
console.log( "3. " + value ); // 3. Butter🧈
throw( "Wait! I'm lactose intolerant🤦♂️" );
})
// 4th: catches error thrown by any of the above `then()`s.
.then( undefined, reason => {
console.log( reason );
throw( "Cancel that list and make a new one!" );
})
// 5th: catches errors thrown only by the above `then()`.
.then( undefined, reason => {
console.log( reason );
return "Fruits🍎";
})
// 6th
.then( value => {
console.log( "1. " + value ); // 1. Fruits🍎
return "Veggies🥕";
})
// 7th
.then( value => {
console.log( "2. " + value ); // 2. Veggies🥕
return "That's it...";
});
/*
OUTPUT:
1. Cheese🧀
2. Milk🥛
3. Butter🧈
Wait! I'm lactose intolerant🤦♂️
Cancel that list and make a new one!
1. Fruits🍎
2. Veggies🥕
*/
thingsToBuyPromise
gets fulfilled with the value "Cheese". This value is passed to the 1st then()
's fulfilled handler. This handler returns another value "Milk" which fulfils the returned promise from this 1st then()
. This invokes the fulfilled handler of the 2nd then()
which receives the value "Milk" and returns another value "Butter". This fulfills the 2nd then()
's returned promise. This in turn invokes the fulfilled handler of the 3rd then()
which unfortunately throws an error. This error is caught by the rejected handler of the 4th then()
. This then()
also throws an error which is caught by the 5th then()
. By now, you can probably guess how things progress.then()
from the chain and see what happens. SPOILER ALERT!! The error thrown by the 3rd then()
will result in an uncaught exception since there will be no rejected handler in any of the subsequent then()
methods to catch the error. The 6th and 7th then()
's handlers won't be executed at all because of the error.then()
to undefined
in the above example, then its simply because we are only interested in catching errors in that part of the chain. In fact, the Promise API exposes a catch()
method which does exactly that. Let's check it out!then()
without a fulfilled handler: then(undefined, handleRejected){...}
. In fact, this is exactly how catch()
internally operates i.e it calls a then()
with the 1st argument as undefined
and a rejected handler function as the 2nd argument. This handler function is the only input that catch()
accepts.var promise = new Promise( (resolve, reject) => {
setTimeout(() => {
reject( "something went wrong🤦♂️" );
}, 1000);
});
var handleRejected = reason => { console.log(reason); }
promise.catch( handleRejected );
/*
OUTPUT:
something went wrong🤦♂️
*/
then()
, catch()
also returns a promise object and so just like then()
, it can also be chained. Let's modify our chaining example to include a catch()
.var thingsToBuyPromise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "Cheese🧀" );
}, 1000);
});
thingsToBuyPromise
// 1st
.then( value => {
console.log( "1. " + value ); // 1. Cheese🧀
return "Milk🥛";
})
// 2nd
.then( value => {
console.log( "2. " + value ); // 2. Milk🥛
return ("Butter🧈");
})
// 3rd
.then( value => {
console.log( "3. " + value ); // 3. Butter🧈
throw( "Wait! I'm lactose intolerant🤦♂️" );
})
// 4th: catches error thrown by any of the above `then()`s.
.catch( reason => {
console.log( reason );
throw( "Cancel that list and make a new one!" );
})
// 5th: catches errors thrown only by the above `then()`.
.catch( reason => {
console.log( reason );
return "Fruits🍎";
})
// 6th
.then( value => {
console.log( "1. " + value ); // 1. Fruits🍎
return "Veggies🥕";
})
// 7th
.then( value => {
console.log( "2. " + value ); // 2. Veggies🥕
return "That's it...";
});
/*
OUTPUT:
1. Cheese🧀
2. Milk🥛
3. Butter🧈
Wait! I'm lactose intolerant🤦♂️
Cancel that list and make a new one!
1. Fruits🍎
2. Veggies🥕
*/
then()
from the previous example with a catch()
. The rest is exactly the same. But it is definitely more convenient and looks much cleaner this way without having to specify undefined
anywhere.then()
and catch()
methods one after the other, in the promise chain.catch()
method can catch errors that are:reject()
in the executor function andthen()
or catch()
methods higher up in the promise chain.resolve()
or reject()
functions. Consider the following example. We throw an error before calling resolve()
. This rejects the promise with the reason specified in the error thrown. Since the promise is rejected, catch()
's handler gets invoked as expected.var promise = new Promise( (resolve, reject) => {
throw( "something went wrong🤦♂️" );
resolve();
});
promise.catch(
reason => { console.log( reason ); }
);
/* OUTPUT
something went wrong🤦♂️
*/
resolve()
with reject()
, then the same thing will happen. The promise will get rejected with the reason specified in the error thrown instead of the reason passed to the reject()
function.resolve()
or reject()
, then the error is silenced.var promise = new Promise( (resolve, reject) => {
resolve( "fulfilled😇" );
throw( "something went wrong🤦♂️" ); // silenced
});
promise.then(
value => { // will be executed
console.log( value );
},
reason => { // won't be executed
console.log( reason );
}
);
/* OUTPUT
fulfilled😇
*/
rejected
. But we have already called resolve()
and the promise has been fulfilled
. Once settled, the state of the promise cannot change which is why the error is silenced. The same thing will happen if we use reject()
instead of resolve()
in the above example. The promise will be rejected with the reason passed to reject()
and the thrown error will be silenced. resolve()
or reject()
is the last thing you do inside the executor function.catch()
is able to catch, there is one scenario where catch()
won't work. It won't be able to catch errors that occur in your asynchronous code. Consider the following example:var promise = new Promise( (resolve, reject) => {
setTimeout(() => {
// this is async code. Any errors thrown here will not be caught.
throw( "something went wrong🤦♂️" );
resolve( "fulfilled😇" );
}, 1000);
});
var handleRejected = reason => { console.log(reason); };
// the rejected handler never gets invoked.
promise.catch( handleRejected );
/*
Uncaught something went wrong🤦♂️
*/
setTimeout()
callback before we can call resolve()
and fulfill the promise. It is not directly inside the executor function as we have seen in the previous examples. You can say that the promise is not aware about this error which is why this error is not caught by our catch()
handler function and results in an uncaught exception. catch()
will only catch errors that are:resolve()
or reject()
functionsreject()
on the original promise andthen()
or catch()
higher up in the promise chain.catch()
method, we are bound to have a finally()
method as well. The main purpose with this method is to execute cleanup code that should be run irrespective of whether the promise was fulfilled or rejected. finally()
method's handler. We could get away with placing this code in both the handlers in a then()
but that would lead to duplication which is not good coding practice. finally()
method accepts a single function as an input. But unlike the handlers in then()
and catch()
, finally()
's input function does not accept any arguments. This is because this function will be invoked for both, fulfilled
and rejected
states and it won't have a way to determine whether the value it receives is a fulfilled value or rejection error reason.var promise = new Promise( (resolve, reject) => {
setTimeout(() => {
resolve( "fulfilled😇" );
}, 1000);
});
var handleFinally = () => {
console.log( "finally handler invoked" );
}
promise.finally( handleFinally );
/*
finally handler invoked
*/
then()
, finally()
also returns a promise object so it can also be chained. But there are some differences between then()
and finally()
in the way the returned promise is settled.var origPromise = new Promise( (resolve, reject) => {
resolve( "fulfilled😇" );
});
var handleFinally = () => "fulfilled by finally";
var returnedPromise = origPromise.finally( handleFinally );
// run after 1 second so that returnedPromise gets settled.
setTimeout( () => {
console.log( returnedPromise );
}, 1000 );
/*
Promise {
<state>: "fulfilled",
<value>: "fulfilled😇"
}
*/
then()
, the returned promise from then()
got fulfilled with the value returned from its handlers. But in the above example, returnedPromise
from finally()
gets fulfilled with the same value as origPromise
and not with the value that its handler function returned. This is because just like the finally()
input function does not accept any inputs, finally()
is not expected to return anything as well. The expectation is that it'll perform some basic cleanup and not have any affect in the flow of information through the promise chain. This is why any value we return in the finally
handler will be ignored.finally()
is no exception(see what I did there😎). So if an error occurs inside the finally()
handler function, then returnedPromise
will get rejected with the error reason.var origPromise = new Promise( (resolve, reject) => {
resolve( "fulfilled" );
});
var handleFinally = () => { throw( "something went wrong🤦♂️" ) };
var returnedPromise = origPromise.finally( handleFinally );
// execute after 1 second so that returnedPromise gets settled.
setTimeout( () => {
console.log( returnedPromise );
}, 1000 );
/*
Uncaught (in promise) something went wrong🤦♂️
Promise {
<state>: "rejected",
<reason>: "something went wrong🤦♂️"
}
*/
then()
, catch()
and finally()
, but a typical promise chain looks like this......
...
.then( handleFulfilled1 )
.then( handleFulfilled2 )
.then( handleFulfilled3 )
.catch( handleRejected )
.finally( handleSettled )
then()
handler in the promise chain. We perform our error handling using catch()
towards the end of the promise chain and at the end, we perform our cleanup using finally()
. Also, in practice, its recommended to use then()
for handling fulfillment and catch()
for rejection scenarios. This is why we have not included the rejection handlers in the above then()
calls.fetch()
Web API(that uses promises) for making a network request to fetch some data and then run it through a promise chain and see what that looks like.fetch("https://api.github.com/users/saurabh-misra/repos")
// parse the JSON response into a JS object
.then( response => response.json() )
// log the name of one of the repos
.then( repos => {
console.log( "Repo name: ", repos[2].name );
})
.catch( reason => console.error( reason ) )
.finally( () => console.log( "all done" ) );
/*
Repo Name: pomodoro-timer
all done
*/
then()
parses the response into a JS object and the 2nd logs the name of a specific repo on to the console. We have catch()
in place if anything goes wrong and a finally()
to perform any cleanup if we need to.