20
loading...
This website collects cookies to deliver better user experience
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
PENDING, initial state when an operation is in progress
FULFILLED, define that the operation was successful
REJECTED, denotes a failure in an operation
Note: A promise is said to be settled when it is either fulfilled or rejected. (we are going to use this term a lot in this article)
const promise = new Promise((resolve, reject) => {
/*
Your code logic goes here and you call resolve(value)
or reject(error) to resolve or reject the promise
*/
})
promise.then((value) => {
// Code logic on success of an operation
}).catch(error => {
// Code logic on failure of an operation
}).finally(() => {
// Code logic to be executed after completion of operation
})
state
: can be either be PENDING
, FULFILLED
or REJECTED
handlers
: stores callbacks of then, catch, finally methods. (Handlers will only be executed when a promise is settled.)
value
: resolve or rejected value.
reject
and resolve
methods passed as parameters to it.const STATE = {
PENDING: 'PENDING',
FULFILLED: 'FULFILLED',
REJECTED: 'REJECTED',
}
class MyPromise {
constructor(callback) {
// Initial state of Promise is empty
this.state = STATE.PENDING;
this.value = undefined;
this.handlers = [];
// Invoke callback by passing the _resolve and the _reject function of our class
try {
callback(this._resolve, this._reject);
} catch (err) {
this._reject(err)
}
}
_resolve = (value) => {}
_reject = (error) => {}
then(onSuccess, onFail) {
}
catch(onFail) {
}
finally(callback) {
}
}
_resolve()
or _reject()
set the state
of promise to FULFILLED
or REJECTED
respectively, updates the value
property and executes the attached handlers.Note: Nothing happens if we try to call _resolve()
or _reject()
on an already settled Promise.
_resolve = (value) => {
this.updateResult(value, STATE.FULFILLED);
}
_reject = (error) => {
this.updateResult(error, STATE.REJECTED);
}
updateResult(value, state) {
// This is to make the processing async
setTimeout(() => {
/*
Process the promise if it is still in a pending state.
An already rejected or resolved promise is not processed
*/
if (this.state !== STATE.PENDING) {
return;
}
// check is value is also a promise
if (isThenable(value)) {
return value.then(this._resolve, this._reject);
}
this.value = value;
this.state = state;
// execute handlers if already attached
this.executeHandlers();
}, 0);
}
isThenable(value)
in the above code?isThenable
function checks if value is an instance of MyPromise
or it is an object containing a then
function.function isThenable(val) {
return val instanceof MyPromise;
}
// or
function isThenable(value) {
if (typeof value === "object" && value !== null && value.then && typeof value.then === "function") {
return true;
}
return false;
}
then()
method takes two arguments as callbacks onSuccess
and onFail
. onSuccess
is called if Promise was fulfilled and onFail
is called if Promise was rejected.“Remember that Promises can be chained”.
The essence of Promise chaining is that the then()
method returns a new Promise object. That is how promises can be chained. This is especially useful in scenarios where we need to execute two or more asynchronous operations back to back, where each subsequent operation starts when the previous operation succeeds, with the result from the previous step.
then()
are stored in handlers
array using addHandlers
function. A handler is an object {onSuccess, onFail}
which will be executed when a promise is settled.then()
looks like this:then(onSuccess, onFail) {
return new MyPromise((res, rej) => {
this.addHandlers({
onSuccess: function(value) {
// if no onSuccess provided, resolve the value for the next promise chain
if (!onSuccess) {
return res(value);
}
try {
return res(onSuccess(value))
} catch(err) {
return rej(err);
}
},
onFail: function(value) {
// if no onFail provided, reject the value for the next promise chain
if (!onFail) {
return rej(value);
}
try {
return res(onFail(value))
} catch(err) {
return rej(err);
}
}
});
});
}
addHandlers(handlers) {
this.handlers.push(handlers);
this.executeHandlers();
}
executeHandlers() {
// Don't execute handlers if promise is not yet fulfilled or rejected
if (this.state === STATE.PENDING) {
return null;
}
// We have multiple handlers because add them for .finally block too
this.handlers.forEach((handler) => {
if (this.state === STATE.FULFILLED) {
return handler.onSuccess(this.value);
}
return handler.onFail(this.value);
});
// After processing all handlers, we reset it to empty.
this.handlers = [];
}
catch()
is implemented using then()
. We call then()
method with the onSuccess
callback as null
and pass onFail
callback as second argument./*
Since then method take the second function as onFail,
we can leverage it while implementing catch
*/
catch(onFail) {
return this.then(null, onFail);
}
finally()
method, let us understand its behaviour first (Took me sometime to understand it myself).The finally()
method returns a Promise
. When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise
has been dealt with.
The finally()
method is very similar to calling .then(onFinally, onFinally)
however there are a couple of differences:
When creating a function inline, you can pass it once, instead of being forced to either declare it twice, or create a variable for it
Unlike Promise.resolve(2).then(() => {}, () => {})
(which will be resolved with undefined
), Promise.resolve(2).finally(() => {})
will be resolved with 2
.
Similarly, unlike Promise.reject(3).then(() => {}, () => {})
(which will be fulfilled with undefined
), Promise.reject(3).finally(() => {})
will be rejected with 3
.
finally()
method returns a Promise which will be settled with previous fulfilled
or rejected
value.// Finally block returns a promise which fails or succeedes with the previous promise resove value
finally(callback) {
return new MyPromise((res, rej) => {
let val;
let wasRejected;
this.then((value) => {
wasRejected = false;
val = value;
return callback();
}, (err) => {
wasRejected = true;
val = err;
return callback();
}).then(() => {
// If the callback didn't have any error we resolve/reject the promise based on promise state
if(!wasRejected) {
return res(val);
}
return rej(val);
})
})
}
then()
, catch()
, finally()
methods which are the instance methods. There are static methods as well which I will try to cover in my future posts.