I have decided to start back with how it works series of articles. Here is the first of many to come. I am pretty sure you are using/used the promise in Javascript many times, either knowingly or not. Let us see what is a Promise and how it works under the hood by writing a custom Promise function.
In Javascript, a Promise is an Object that is used to represent the eventual completion/failure of an asynchronous operation.
pending, fulfilled, and rejected.resolve or reject only once.thenable and catchable.resolve() and reject().finally() method which is called when the promise is settled.We will start with a simple example of a promise.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
}, 5000);
});
myPromise
.then((result) => {
console.log(result); // "1" after 5 seconds
})
.catch((error) => {
console.log(error);
})
.finally(() => {
console.log('Finally'); // Logs when promise is settled
});
The above code snippet is a simple example that is self-explanatory if not let me explain. After 5 seconds, the above code will resolve the myPromise constructor. and it will log 1 in the console and also it will log Finally. Simple right, I can resolve a promise with resolve callback and reject a promise with reject callback and finally is called when the promise is settled.
We are going to understand and write CustomPromise function in 4 steps.
.then(), .catch() and .finally() methods, we need to add then, catch and finally methods in this keyword of function and each method should return this to make it chainable.function CustomPromise(callback) {
this.then = () => this;
this.catch = () => this;
this.finally = () => this;
}
const callbackFn = () => {};
const myPromise = new CustomPromise(callbackFn);
myPromise
.then(() => console.log)
.catch(() => console.log)
.finally(() => console.log);
Wow, we just learned how to create a CustomPromise function and how to make it chainable like an actual Promise object in Javascript.
function CustomPromise(callback) {
let thenCallbacksArr = []; // To hold as many then as possible
let catchCallback = null; // To hold catch callback
this.then = (myThenCallback) => {
thenCallbacksArr.push(myThenCallback); // Stores all then callbacks
return this;
};
this.catch = (myCatchCallback) => {
catchCallback = myCatchCallback;
return this;
};
this.finally = () => {
return this;
};
function resolve() {}
function reject() {}
callback(resolve, reject);
}
catch() method and discards the other catch methods.finally() method doesn't accept any parameters.finally() method.function CustomPromise(callback) {
let thenCallbacksArr = []; // To hold as many then as possible
let catchCallback = null; // To hold catch callback
let promiseState = 'pending'; // To keep track of promise state (resolved/rejected)
let called = false; // To know if resolve/reject is called or not
this.then = (myThenCallback) => {
thenCallbacksArr.push(myThenCallback); // Stores all then callbacks
return this;
};
this.catch = (myCatchCallback) => {
catchCallback = myCatchCallback;
return this;
};
this.finally = (myFinallyCallback) => {
// Post resolve or reject, we will call the finally method
if (called && promiseState !== 'pending') {
myFinallyCallback();
}
return this;
};
function resolve(arg) {
promiseState = 'resolved';
// If resolve/reject not called
if (!called) {
called = true;
// We need to iterate thenCallbackArr and pass prev result to then callback
thenCallbacksArr.reduce((result, cb) => cb(result), arg);
}
}
function reject(error) {
promiseState = 'rejected';
// If reject/resolve not called
if (!called) {
called = true;
// Catch is optionally so check if its present
if (typeof catchCallback === 'function') {
catchCallback(error);
}
}
}
callback(resolve, reject);
}
Above CustomPromise function will work just fine but when a promise is resolved/rejected before we can assign a then or catch or finally method, there is no code to handle that. That's what we will do in step 4.
function CustomPromise(callback) {
let thenCallbacksArr = []; // To hold as many then as possible
let catchCallback = null; // To hold catch callback
let promiseState = 'pending'; // To keep track of promise state (resolved/rejected)
let called = false; // To know if resolve/reject is called or not
let resolveArguments = null; // To hold resolve arguments
let rejectArguments = null; // To hold reject error
this.then = (myThenCallback) => {
thenCallbacksArr.push(myThenCallback); // Stores all then callbacks
// If resolve callback method is already called, but then was not atached
if (called && promiseState === 'resolved') {
// Since there can ba many then's, call the first and store the returned result
// and pass resolveArguments to it
var firstThenFunzInArr = thenCallbacksArr.shift();
resolveArguments = firstThenFunzInArr(resolveArguments);
}
return this;
};
this.catch = (myCatchCallback) => {
catchCallback = myCatchCallback;
// If reject callback method is already called, but catch was not atached
if (called && promiseState === 'rejected') {
catchCallback(rejectArguments);
}
return this;
};
this.finally = (myFinallyCallback) => {
thenCallbacksArr.push(myFinallyCallback);
// Post resolve or reject, we will call the finally method
if (called && promiseState !== 'pending') {
myFinallyCallback();
}
return this;
};
function resolve(arg) {
promiseState = 'resolved';
// If resolve/reject not called
if (!called) {
called = true;
resolveArguments = arg;
// We need to iterate thenCallbackArr and pass prev result to then callback
thenCallbacksArr.reduce((result, cb) => cb(result), arg);
}
}
function reject(error) {
promiseState = 'rejected';
// If reject/resolve not called
if (!called) {
called = true;
rejectArguments = error;
// Catch is optionally so check if its present
if (typeof catchCallback === 'function') {
catchCallback(error);
}
}
}
callback(resolve, reject);
}
If you followed through with the code, you would have understood now how a promise works internally. Also, this is purely for educational purposes only, and dont ask questions like "Write a polyfill for the promise" in interviews.
I would have also missed a few edge cases. But you get it right? How it works. Hope you liked this post.
If you did, do share it with your friends and colleagues.