Promise API
Promises/Async/Await: Promise API
What is a Promise in JavaScript and why is it useful?
View Answer:
Can you explain the three states of a Promise?
View Answer:
A Promise in JavaScript can be in one of three states:
- Pending: Initial state, neither fulfilled nor rejected.
- Fulfilled: Meaning that the operation completed successfully.
- Rejected: Meaning that the operation failed.
Here's an example:
// 1. Pending
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve('Done!'), 1000);
});
console.log(promise); // Promise {<pending>}
// 2. Fulfilled
promise.then(value => console.log(value)); // After 1 second, outputs: "Done!"
// To illustrate a rejected state, let's create another promise
let rejectedPromise = new Promise((resolve, reject) => {
setTimeout(() => reject('Something went wrong!'), 1000);
});
// 3. Rejected
rejectedPromise.catch(error => console.error(error)); // After 1 second, outputs: "Something went wrong!"
In this example, promise
starts in the Pending state, then moves to the Fulfilled state after one second. rejectedPromise
also starts in the Pending state, then moves to the Rejected state after one second.
Once a Promise is either Fulfilled or Rejected, it is considered settled and its state cannot change. The Promise is said to be immutable after it is settled.
Note that in practice, you can't directly access the state of a Promise, but its state is reflected in how it behaves. The Promise API ensures that the behavior of the Promise is consistent with its state.
What are the two main methods used to handle Promises?
View Answer:
What are the Promise class's six static methods?
View Answer:
Can you explain the function of the Promise.all() static method?
View Answer:
Syntax: Promise.all(iterable);
Promise.all([
new Promise((resolve) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve) => setTimeout(() => resolve(3), 1000)), // 3
]).then(console.log); // 1,2,3 when promises are ready: each promise contributes an array member
// Another Example:
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
How does the map method work with the Promise.all() static method?
View Answer:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig',
];
// map every url to the promise of the fetch
let requests = urls.map((url) => fetch(url));
// Promise.all waits until all jobs are resolved
Promise.all(requests).then((responses) =>
responses.forEach((response) =>
console.log(`${response.url}: ${response.status}`)
)
);
Use Case: A common trick is to map an array of job data into an array of promises and then wrap that into Promise.all.
What happens when there is an error with the Promise.all() method?
View Answer:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Whoops!')), 2000)
),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
]).catch(console.log); // Error: Whoops!
Can Promise.all() handle regular values, not just Promises, within the input iterable?
View Answer:
Syntax: Promise.allSettled(iterable);
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
}),
2,
3,
]).then(console.log); // 1, 2, 3
Can you explain the function of the static method, Promise.allSettled()?
View Answer:
Syntax: Promise.allSettled(iterable);
let promises = [
Promise.resolve('promise 1'),
Promise.reject('promise 2 failed'),
Promise.resolve('promise 3'),
];
Promise.allSettled(promises)
.then(results => {
results.forEach(result => console.log(result.status));
})
.catch(error => console.error(error));
In this code, Promise.allSettled()
waits for all promises to settle, either fulfilled or rejected. The status of each promise is then logged. Unlike Promise.all()
, Promise.allSettled()
does not reject if one promise fails; instead, it gives the status of each promise.
Can you explain the function of the Promise.race static method?
View Answer:
Syntax: Promise.race(iterable);
let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'one'));
let promise2 = new Promise((resolve, reject) => setTimeout(resolve, 100, 'two'));
Promise.race([promise1, promise2])
.then(value => console.log(value)) // lots "two"
.catch(error => console.error(error));
In this example, Promise.race
takes an array of two promises. The 'two' string is logged to the console because promise2 resolves first due to its shorter timeout.
Can you explain the function of the Promise.any static method?
View Answer:
const promise1 = Promise.reject(0);
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));
const promises = [promise1, promise2, promise3];
Promise.any(promises).then((value) => console.log(value));
// expected output: "quick"
//////////////////////////////////
// Here is an example when all promises fail:
Promise.any([
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Ouch!')), 1000)
),
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Error!')), 2000)
),
]).catch((error) => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error
});
Can you explain the Promise.resolve static method's function?
View Answer:
Syntax: Promise.resolve(value);
function maybeAsync(value) {
return Promise.resolve(value).then(result => {
// Now we can safely use .then() whether `value` was initially a Promise or not
console.log(result);
});
}
// This will print: "Sync value"
maybeAsync("Sync value");
// This will print: "Async value" (after one second)
maybeAsync(new Promise(resolve => setTimeout(() => resolve("Async value"), 1000)));
As you can see, Promise.resolve can be a useful method when working with JavaScript Promises.
This function flattens nested layers of promise-like objects (e.g., a promise that resolves to a promise that resolves to something) into a single layer. Methods Promise.resolve and Promise.reject are rarely needed in modern code because async/await syntax makes them somewhat obsolete.
Can you explain the function of the Promise.reject static method?
View Answer:
Syntax: Promise.reject(reason);
Here is a simple example of Promise.reject
:
// Let's create a Promise that is immediately rejected with a specific reason.
let p = Promise.reject('Something went wrong!');
// You can then use the Promise as you would any other.
// Since it's a rejected promise, .catch() is used to handle the error.
p.catch(reason => console.log(reason)); // Outputs: "Something went wrong!"
This Promise will immediately move to the rejected
state, triggering any .catch
handlers as soon as the event loop is free.
Just like Promise.resolve
, Promise.reject
can be useful when writing functions that handle both synchronous and asynchronous errors. Here's an example:
function maybeAsyncError(value, throwError) {
if (throwError) {
return Promise.reject(new Error('There was an error!'));
}
return Promise.resolve(value);
}
maybeAsyncError('Hello, world!', false)
.then(value => console.log(value)) // Outputs: "Hello, world!"
.catch(error => console.error(error));
maybeAsyncError('Hello, world!', true)
.then(value => console.log(value))
.catch(error => console.error(error)); // Outputs: "Error: There was an error!"
In this example, the maybeAsyncError
function either resolves with the provided value or rejects with an Error, based on the throwError
argument. This allows it to be used with Promise-based code regardless of whether an error occurs.
Methods Promise.resolve and Promise.reject are rarely needed in modern code because async/await syntax makes them somewhat obsolete.
What is the purpose of the finally() method in a Promise?
View Answer:
let p = new Promise((resolve, reject) => {
setTimeout(() => resolve('Hello, world!'), 1000);
});
p.then(value => console.log(value)) // Outputs: "Hello, world!"
.catch(error => console.error(error))
.finally(() => console.log('This is called no matter what.')); // Outputs: "This is called no matter what."
In this case, finally()
is called after then()
, no matter the outcome of the Promise. If the Promise was rejected and you had a catch()
method, finally()
would still be called:
let p = new Promise((resolve, reject) => {
setTimeout(() => reject('There was an error!'), 1000);
});
p.then(value => console.log(value))
.catch(error => console.log(error)) // Outputs: "There was an error!"
.finally(() => console.log('This is called no matter what.')); // Outputs: "This is called no matter what."
In this case, the catch()
method is called because the Promise is rejected, but finally()
is still called afterwards.
It's important to note that finally()
does not receive any arguments, as it's not meant to process the Promise's result or error. Instead, it's meant for cleanup tasks that need to happen no matter what.
What is the difference between Promise.all() and Promise.race()?
View Answer:
Here is an example demonstrating the difference:
let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'one'));
let promise2 = new Promise((resolve, reject) => setTimeout(resolve, 1000, 'two'));
let promise3 = new Promise((resolve, reject) => setTimeout(reject, 1200, 'I failed'));
// Promise all
Promise.all([promise1, promise2, promise3])
.then(values => console.log(values))
.catch(error => console.log("Promise.all error:", error));
// Promise race
Promise.race([promise1, promise2, promise3])
.then(value => console.log(value))
.catch(error => console.log("Promise.race error:", error));
In this example:
The Promise.all()
call will end up being rejected, because promise3
is rejected before promise1
and promise2
are both resolved. It will print "Promise.all error: I failed" The Promise.race()
call will resolve with the value 'one'
, because promise1
resolves before either of the other two Promises settle. It will print 'one'
.
How can you create a resolved or rejected Promise immediately?
View Answer:
let resolvedPromise = Promise.resolve('Resolved!');
resolvedPromise.then(value => console.log(value)); // Output: 'Resolved!'
let rejectedPromise = Promise.reject('Rejected!');
rejectedPromise.catch(reason => console.log(reason)); // Output: 'Rejected!'
In this code, Promise.resolve
creates a promise that is resolved with the given value 'Resolved!', and Promise.reject
creates a promise that is rejected with the given reason 'Rejected!'.
What is a Promise chain's return value?
View Answer:
How do you handle multiple Promises concurrently without waiting for the previous one to complete?
View Answer:
let promise1 = new Promise((resolve, reject) => setTimeout(resolve, 500, 'one'));
let promise2 = new Promise((resolve, reject) => setTimeout(resolve, 200, 'two'));
let promise3 = new Promise((resolve, reject) => setTimeout(resolve, 300, 'three'));
Promise.all([promise1, promise2, promise3])
.then(values => console.log(values))
.catch(error => console.error(error));
In this code, Promise.all
takes an array of promises. The promises are run concurrently, not waiting for the previous one to complete. The array ['one', 'two', 'three'] will be logged to the console once all promises have resolved.
What is the difference between using async/await and Promises?
View Answer:
Example using Promises:
function fetchData(url) {
fetch(url)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
}
fetchData('https://api.example.com/data');
And here's the equivalent using async/await:
async function fetchData(url) {
try {
let response = await fetch(url);
let data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
fetchData('https://api.example.com/data');
In both cases, we're fetching data from a URL and logging it. The async/await version is arguably easier to read and write because it avoids explicit Promise chaining.
What is the role of the executor function in creating a new Promise?
View Answer:
let promise = new Promise((resolve, reject) => {
let success = true; // Here you'd typically perform an async task
if (success) {
resolve('Task completed successfully');
} else {
reject('Error: Task could not complete');
}
});
promise
.then(value => console.log(value)) // Logs: 'Task completed successfully'
.catch(error => console.log(error));
In this code, the executor function decides whether to call resolve()
or reject()
. If resolve()
is called, then the .then()
block will execute. If reject()
is called, then the .catch()
block will execute.
How can you handle errors in a Promise chain?
View Answer:
let p = new Promise((resolve, reject) => {
setTimeout(() => reject('There was an error!'), 1000);
});
p.then(value => console.log(value))
.catch(error => console.error(error)); // Outputs: "There was an error!"
In this case, because the Promise is rejected, the .catch()
handler is called.
You can also use .catch()
to handle errors that are thrown in a .then()
handler:
let p = Promise.resolve('Hello, world!');
p.then(value => {
throw new Error('There was an error in the handler!');
})
.catch(error => console.error(error)); // Outputs: "Error: There was an error in the handler!"
In this case, the .then()
handler throws an error, which is then caught and handled by the .catch()
handler.
Note that .catch()
also returns a Promise. If you return a value in a .catch()
handler, it will be the resolution value for that returned Promise. If you throw an error in a .catch()
handler, the returned Promise will be rejected with that error.
Promise.reject('Initial error')
.catch(error => {
console.error(error); // Outputs: "Initial error"
return 'Recovered from error';
})
.then(value => console.log(value)) // Outputs: "Recovered from error"
.catch(error => console.error('Should not be called'));
In this case, even though the initial Promise is rejected, the .catch()
handler recovers from the error by returning a new value. This makes the Promise returned by .catch()
become resolved, so the .then()
handler is called next, not the .catch()
handler.
What happens if you don't handle a rejected Promise?
View Answer:
let p = new Promise((resolve, reject) => {
setTimeout(() => reject('There was an error!'), 1000);
});
p.then(value => console.log(value));
// No .catch() handler, so the rejection is unhandled.
// Most environments will log something like:
// "UnhandledPromiseRejectionWarning: There was an error!"
In this case, because there is no .catch()
handler to catch the rejected Promise, it becomes an unhandled promise rejection.
To prevent this, you should always handle Promise rejections with a .catch()
handler, even if it's just to log the error:
let p = new Promise((resolve, reject) => {
setTimeout(() => reject('There was an error!'), 1000);
});
p.then(value => console.log(value))
.catch(error => console.error(error)); // Outputs: "There was an error!"
In this case, the .catch()
handler catches the rejected Promise, preventing an unhandled promise rejection.