Introduction
What are Promises?
- Managing Asynchronous Operations: In JavaScript, many operations (like fetching data from a server, reading a file, or waiting for a timer) take time. Promises provide a structured way to handle the results of these asynchronous operations without getting tangled up in messy callbacks.
- A Proxy for Future Values: A Promise is an object that represents the eventual result of an asynchronous operation. It’s like a placeholder. Initially, the promise is in a “pending” state, but eventually, it will either:
- Fulfilled: The operation was successful, and a value is available.
- Rejected: The operation failed, and you get an error explaining why.
Key Methods
.then()Used to handle the successful resolution of a Promise. It takes a callback function that receives the resolved value..catch()Used to handle errors. It takes a callback function that receives the error object..finally()Executes a callback function regardless of whether a promise is fulfilled or rejected. Often used for cleanup tasks.
import console from 'console';
import { setTimeout } from 'timers';
function delay(ms: number, shouldResolve: boolean): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldResolve) {
resolve('Promise resolved');
} else {
reject('Promise rejected');
}
}
, ms);
});
}
delay(1000, true).then(function(message) {
console.log(message); // This will log "Promise resolved"
}).catch(function(error) {
console.error(error); // This won't be called in this case
});When you create a new Promise in TypeScript, you pass an executor function to the Promise constructor. This executor function takes two arguments: a resolve function and a reject function.
Here’s what each argument is for:
resolve: This is a function that you call when the asynchronous operation completes successfully. You call this function with the result of the operation.reject: This is a function that you call when the asynchronous operation fails. You call this function with the reason for the failure, which is typically an Error object.
(resolve, reject) => { ... }
- This is the executor function. It has two parameters:
resolve: A function you call when the asynchronous operation has successfully completed. You must pass it the value that the Promise should resolve to.reject: A function you call if the asynchronous operation fails. You pass it anErrorobject that explains the reason for the failure.
Code for JavaScript Promises
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
if (value instanceof MyPromise) {
value.then(resolve, reject);
} else {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(callback => callback(value));
}
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.value = reason;
this.onRejectedCallbacks.forEach(callback => callback(reason));
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
const handleFulfilled = (value) => {
try {
const result = onFulfilled(value);
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (error) {
reject(error);
}
};
const handleRejected = (reason) => {
try {
const result = onRejected(reason);
if (result instanceof MyPromise) {
result.then(resolve, reject);
} else {
reject(result);
}
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
handleFulfilled(this.value);
} else if (this.state === 'rejected') {
handleRejected(this.value);
} else {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
}
}Design Choices
- The
MyPromiseclass has aconstructorthat takes anexecutorfunction. This function is immediately executed and is passed two functions:resolveandreject. These functions allow the executor to indicate the success or failure of the promise. - The
stateproperty is used to track the status of the promise. It can be ‘pending’, ‘fulfilled’, or ‘rejected’. - The
valueproperty is used to store the result of the promise. - The
onFulfilledCallbacksandonRejectedCallbacksarrays are used to store callback functions that will be executed when the promise is fulfilled or rejected.
- New Promise from
thenfunction:- The
thenmethod returns a new promise. This is a key feature of promises that enables chaining. The returned promise resolves or rejects based on the outcome of theonFulfilledoronRejectedcallbacks. If these callbacks return a value, the returned promise is resolved with that value. If they throw an error, the returned promise is rejected with that error.
- The
- Need for a Callback Array:
- The
onFulfilledCallbacksandonRejectedCallbacksarrays are needed because thethenmethod can be called multiple times on the same promise. Each timethenis called, a new callback is added to the respective array. When the promise is settled (either fulfilled or rejected), all the registered callbacks are called. This is known as “observation” of a promise. - These arrays are also useful when the
thenmethod is called after the promise has already settled. In this case, the callback is immediately called with the promise’s value or reason
- The
myPromise = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve('success');
}, 1000);
});
myPromise.then((value) => {
console.log(value);
});