Promises
Promises are the basics of asynchronous programming in JavaScript, and are very important to master.
What is Asynchronous Programming?
Asynchronous programming, or in short, async programming, is a method of programming which enables different parts of code to run at changing times, instead of immediately.
This is mostly required when we want to fetch information from some remote server, and write code which does something with what the server returned:
function getServerStatus() {
const result = fetch("/server/status");
// THIS WILL NOT WORK!
console.log("The status from the server is: ", result.ok);
}
In many programming languages such as Python, this approach would work, because functions are by default synchronous functions.
In JavaScript, most APIs which require waiting for a function to do something,
are asynchronous by default which means that this code will not
do what we think it will do, since the fetch
function is asynchronous, and therefore will
return something which is not exactly the result, but will eventually be the result.
This "thing" which is returned from the fetch
function is called a Promise in JavaScript.
To make the code above work, we will need to write the function in the following manner:
function getServerStatus() {
const result = fetch("/server/status");
result.then(function(status) {
console.log("The status from the server is: ", status.ok);
});
}
Notice that we used the then
function here, which is one of the methods of a Promise
.
The Promise Object
A Promise is a native JavaScript object which has two traits:
1. It receives a single argument which is a function. This function needs to have two arguments,
a resolve
function and a reject
function. The code written inside the promise needs
to use one of these two functions.
2. It can be waited on using the then
method (and other similar methods), or the await
statement. (The async/await statements have a separate tutorial).
An asynchronous function is defined by a function, which instead of returning the value
it was supposed to return, it returns a Promise
object, which will eventually resolve and
give the user the answer.
For example, let's say that we would like to calculate the sum of two numbers, but by
writing a function which returns a Promise
and not the value.
function sumAsync(x, y) {
const p = new Promise((resolve, reject) => {
// this resolves the promise we just created with the output of x+y
resolve(x + y);
});
// This returns the promise, not the value
return p;
}
// let's use the function now
sumAsync(5, 7).then((result) => {
console.log("The result of the addition is:", result);
});
When can this be very useful? When the calculation needs to happen indirectly, for example
after waiting a while or when retrieving information from the server using the fetch
command for example.
Let's modify the example to resolve the solution only after a half a second:
function sumAsync(x, y) {
console.log("1. sumAsync is executed");
const p = new Promise((resolve, reject) => {
// run this in 500ms from now
setTimeout(() => {
console.log("4. Resolving sumAsync's Promise with the result after 500ms");
resolve(x + y);
}, 500);
// we don't need to return anything
console.log("2. sumAsync Promise is initialized");
});
console.log("3. sumAsync has returned the Promise");
return p;
}
// let's use the function now
sumAsync(5, 7).then((result) => {
console.log("5. The result of the addition is:", result);
});
Rejecting promises
In a synchronous flow, if we want to tell the user that something went wrong so he can
catch an exception, we throw an exception using the throw
argument. When using promises,
we need to trigger the reject
function instead.
Let's say we want to write the same function, but with a rejection if a value is negative:
function sumAsync(x, y) {
return new Promise((resolve, reject) => {
// run this in 500ms from now
setTimeout(() => {
if (x < 0 || y < 0) {
reject("Negative values received");
} else {
resolve(x + y);
}
}, 500);
// we don't need to return anything
});
}
sumAsync(-5, 7).then((result) => {
console.log("The result of the addition is:", result);
}).catch((error) => {
console.log("Error received:", error);
});
Exercise
Write a function which receives a string, and returns a Promise.
The promise should resolve with the uppercase version of the string, but should reject if the string is null.