Promises
Poly promises to bake a cake for my birthday in two weeks. If all goes well and she does not get sick, I will have a cake. If Poly does not feel well, she will not be able to bake the cake for me. A promise is not a guarantee of fulfillment, we do not know its outcome in advance. In programming, there are also tasks with the outcome to be known only in the future.

Promise is an object representing the current state of an asynchronous operation. This is a wrapper for a value unknown at the time the promise is created. It makes it possible to handle the results of asynchronous operations as if they were synchronous: instead of the result of an asynchronous operation, a promise of the future result is returned.
Promises can assume three states:
- Pending: initial state when creating a promise.
- Fulfilled: the operation was completed successfully, with some result.
- Rejected: the operation was rejected with an error.

When a promise is created, it is pending
, after which it can be settled successfully (fulfilled
), returning a result (value), or with an error (rejected
), returning a reason. When a promise enters the fulfilled
or rejected
state, this is forever.
When a promise is fulfilled or rejected, it is said to be settled
. It is simply a term to describe that a promise is in any state other than pending.
Differences between promises and callbacks:
- Callbacks are functions, promises are objects.
- Callbacks are passed as arguments from outer to inner code, whereas promises are returned from inner to outer code.
- Callbacks handle successful or unsuccessful completion of an operation, while promises do not handle anything.
- Callbacks can handle multiple events, and promises are associated with only one event.
Creation
A promise is created as an instance of the Promise
class, which takes a function (executor
) as an argument and calls it immediately, even before the promise is created and returned.
const promise = new Promise((resolve, reject) => {
// Asynchronous operation
});
The executor
function notifies the instance (promise) when and how the related operation is going to complete. In it, you can perform any asynchronous operation; upon completion, you need to call either resolve()
on success (fulfilled
state), or reject()
on error (rejected
state). The return value of this function is ignored.
resolve(value)
is a function to be called upon successful operation. The value of the fulfilled promise will be passed to it as an argument.reject(error)
is a function to be called in case of an error. The value of the rejected promise will be passed to it as an argument.

// Change value of isSuccess variable to call resolve or reject
const isSuccess = true;
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
resolve("Success! Value passed to resolve function");
} else {
reject("Error! Error passed to reject function");
}
}, 2000);
});
A promise (object) in the pending
state will be written to the variable promise
, and two seconds later, after resolve()
or reject()
is called, the promise will change its state to fulfilled
or rejected
, and you will be able to handle it.
then()
method
Code that needs to do something asynchronously creates a promise and returns it. The outer code, having received a promise, hangs up handlers on it. When the process is completed, the asynchronous code makes the promise go to the fulfilled
or rejected
state, after which handlers in the outer code are automatically called.
After the promise is created, its result is handled in callback functions. Code is written in terms of what might happen if the promise is fulfilled or not, without considering the time frame.
then()
takes two arguments: callbacks that will be called when the promise changes its state. They will receive the result of the Promise, a value or error, as arguments.
promise.then(onResolve, onReject)
onResolve(value)
will be called when the promise is successful and will receive its result as an argument.onReject(error)
will be called when the promise throws an error and will receive it as an argument.

In the example, the onResolve
callback will be called two seconds later if the promise is fulfilled, while onReject
will be called two seconds later if the promise is rejected.
// Change value of isSuccess variable to call resolve or reject
const isSuccess = true;
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
resolve("Success! Value passed to resolve function");
} else {
reject("Error! Error passed to reject function");
}
}, 2000);
});
// Will run first
console.log("Before promise.then()");
// Registering promise callbacks
promise.then(
// onResolve will run third or not at all
value => {
console.log("onResolve call inside promise.then()");
console.log(value); // "Success! Value passed to resolve function"
},
// onReject will run third or not at all
error => {
console.log("onReject call inside promise.then()");
console.log(error); // "Error! Error passed to reject function"
}
);
// Will run second
console.log("After promise.then()");
When the onResolve
and onReject
functions’ logic is complex , for convenience they are declared as external functions and passed to the then()
method by name.
catch()
method
In practice, then()
is only used to handle fulfilled promises, while execution errors and handled in a special catch()
method to "catch" errors.

promise.catch(error => {
// Promise rejected
});
The callback will be called when the promise throws an error and will receive it as an argument.
// Change value of isSuccess variable to call resolve or reject
const isSuccess = true;
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
resolve("Success! Value passed to resolve function");
} else {
reject("Error! Error passed to reject function");
}
}, 2000);
});
promise
.then(value => {
console.log(value);
})
.catch(error => {
console.log(error);
});
finally()
method
This method can be useful if you need to execute code after the promise is fulfilled or rejected, regardless of the outcome. It prevents code duplication in the then()
and catch()
handlers.

promise.finally(() => {
// Promise fulfilled or rejected
});
The callback will not receive any arguments because the promise status, fulfilled or rejected, cannot be checked. Here only the code will be executed that must be run in any case.
// Change value of isSuccess variable to call resolve or reject
const isSuccess = true;
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
resolve("Success! Value passed to resolve function");
} else {
reject("Error! Error passed to reject function");
}
}, 2000);
});
promise
.then(value => console.log(value)) // "Success! Value passed to resolve function"
.catch(error => console.log(error)) // "Error! Error passed to reject function"
.finally(() => console.log("Promise settled")); // "Promise settled"
Promises chaining
then()
returns another promise with a value returned by its onResolve
callback. This enables you to build asynchronous chains from promises.

Since the then()
method returns a promise, it may take some time before it is executed, so the rest of the chain will wait. If an error occurs anywhere in the chain, execution of all subsequent then()
is canceled, and control is passed to the catch()
method. Which is why it belongs to the end of the promises chain.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5);
}, 2000);
});
promise
.then(value => {
console.log(value); // 5
return value * 2;
})
.then(value => {
console.log(value); // 10
return value * 3;
})
.then(value => {
console.log(value); // 30
})
.catch(error => {
console.log(error);
})
.finally(() => {
console.log("Final task");
});
Promisification of functions
Let's imagine that you have an asynchronous function that performs an asynchronous operation, for example, a request to the server. In order to handle the result, it should be able to wait for two callbacks, for a successful request and for an error.
const fetchUserFromServer = (username, onSuccess, onError) => {
console.log(`Fetching data for ${username}`);
setTimeout(() => {
// Change value of isSuccess variable to simulate request status
const isSuccess = true;
if (isSuccess) {
onSuccess("success value");
} else {
onError("error");
}
}, 2000);
};
const onFetchSuccess = user => {
console.log(user);
};
const onFetchError = error => {
console.error(error);
};
fetchUserFromServer("Mango", onFetchSuccess, onFetchError);
Now the fetchUserFromServer()
function knows too much about the code that will use its outcome. It expects callbacks and is responsible for calling them under certain conditions. That is, you pass something inside the function (callbacks) and hope that it will work correctly, which is not good.
It is better when the function does not care about the code that will use its result. It just performs an operation and returns its result to outer code. In order to return the result of an asynchronous operation, a promise must be returned from the function. Promisification means transforming a function with callbacks so that it does not accept callbacks, but returns a promise.
const fetchUserFromServer = username => {
return new Promise((resolve, reject) => {
console.log(`Fetching data for ${username}`);
setTimeout(() => {
// Change value of isSuccess variable to simulate request status
const isSuccess = true;
if (isSuccess) {
resolve("success value");
} else {
reject("error");
}
}, 2000);
});
};
fetchUserFromServer("Mango")
.then(user => console.log(user))
.catch(error => console.error(error));
Most modern libraries are promise-based. When a method is called for an asynchronous operation, its result is available as a promise, to which you can add handlers in the then()
and catch()
methods.