Advanced Promises in LWC
Introduction
Before we start, you have to understand what promises are, the difference between then/catch
and async/await
, and the best practices when working with promises. These two articles can help you out:
Promise.all
The
Promise.all()
static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input’s promises are fulfilled (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input’s promises is rejected, with this first rejection reason. ~ [2] MDN Web Docs
Example
const promiseA = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseA works!'
}), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseB works!'
}), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseC works!'
}), 250));
// Promise.all() [...] takes an iterable of promises as input
// it can be an array of promises [promiseA, promiseB, promiseC];
// and returns a single Promise
// single promise can be resolved with then or async/await
// then
Promise.all([promiseA, promiseB, promiseC])
.then(results => {
results.forEach(result => console.log(result));
})
.catch(error => {
console.log(`Error: ${error}`);
});
// or async/await
try {
const results = await Promise.all([promiseA, promiseB, promiseC]);
results.forEach(result => console.log(result));
} catch (error) {
console.log(`Error: ${error}`);
}
OUTPUT
{message: 'promiseA works!'}
{message: 'promiseB works!'}
{message: 'promiseC works!'}
The results
variable is an array ([]) that contains the results of each promise. The order of passed promises indicates the order of results.
promiseA => results[0] => {message: 'promiseA works!'}
promiseB => results[1] => {message: 'promiseB works!'}
promiseC => results[2] => {message: 'promiseC works!'}
What will happen if any of the promises fail?
const promiseA = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseA works!'
}), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseB works!'
}), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseC something happens!'
}), 250));
Promise.all([promiseA(), promiseB(), promiseC()])
.then(results => {
results.forEach(result => console.log(result));
})
.catch(error => {
console.log(`Error: ${JSON.stringify(error)}`);
});
OUTPUT
Error: {"message": "promiseC something happen!"}
Promise.all()
rejects when ANY of the passed promises rejects, with the first rejection reason.
Promise.all – when to use?
It can be useful for aggregating the results of multiple promises. It is typically used when there are multiple related asynchronous tasks that the overall code relies on to work successfully — all of which we want to fulfill before the code execution continues. ~ [2] MDN Web Docs
Use Promise.all()
when you need to get the results of many asynchronous methods at once, and all Promises should be fulfilled successfully (are dependent).
Below you can find an example, where we need to wait until all page settings are loaded at once.
import { LightningElement } from "lwc";
import getUserPermissions from "@salesforce/apex/MyController.getUserPermissions";
import getUserData from "@salesforce/apex/MyController.getUserData";
import getPageSettings from "@salesforce/apex/MyController.getPageSettings";
export default class MyComponent extends LightningElement {
isLoading = true;
userPermissions = [];
userData = {};
pageSettings = {}
connectedCallback() {
Promise.all([
getUserPermissions(),
getUserData(),
getPageSettings()
]).then(results => {
this.userPermissions = results[0];
this.userData = results[1];
this.pageSettings = results[2];
}).catch(err => {
console.error(error);
}).finally(() => {
this.isLoading = false;
});
}
}
Promise.all – best practices
❌❌❌
import { LightningElement } from "lwc";
import getUserPermissions from "@salesforce/apex/MyController.getUserPermissions";
import getUserData from "@salesforce/apex/MyController.getUserData";
import getPageSettings from "@salesforce/apex/MyController.getPageSettings";
export default class MyComponent extends LightningElement {
isLoading = true;
userPermissions = [];
userData = {};
pageSettings = {}
connectedCallback() {
Promise.all([
getUserPermissions(),
getUserData(),
getPageSettings()
]).then(results => {
this.userPermissions = results[0];
this.userData = results[1];
this.pageSettings = results[2];
}).catch(err => {
console.error(error);
}).finally(() => {
this.isLoading = false;
});
}
}
A good practice is to use the Destructuring assignment.
It allows you to unpack values from an array and make the code easier to read instead of using array indexes [0]
, [1]
, etc.
✅✅✅
import { LightningElement } from "lwc";
import getUserPermissions from "@salesforce/apex/MyController.getUserPermissions";
import getUserData from "@salesforce/apex/MyController.getUserData";
import getPageSettings from "@salesforce/apex/MyController.getPageSettings";
export default class MyComponent extends LightningElement {
isLoading = true;
userPermissions = [];
userData = {};
pageSettings = {}
connectedCallback() {
Promise.all([
getUserPermissions(),
getUserData(),
getPageSettings()
]).then(([userPermissions, userData, pageSettings]) => {
this.userPermissions = userPermissions;
this.userData = userData;
this.pageSettings = pageSettings;
}).catch(err => {
console.error(error);
}).finally(() => {
this.isLoading = false;
});
}
}
Promise.allSettled
The
Promise.allSettled()
static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when all of the input’s promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise. ~ [2] MDN Web Docs
const promiseA = new Promise((resolve, reject) => setTimeout(() => reject('promiseA something happened'), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve('promiseB works!'), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => reject('promiseC something happend'), 250));
// Promise.allSettled() [...] takes an iterable of promises as input
// it can be an array of promisses [promiseA, promiseB, promiseC];
// and returns a single Promise
// single promise can be resolved with then or async/await
// then
Promise.allSettled([promiseA(), promiseB(), promiseC()])
.then(results => {
results.forEach(result => console.log(result));
});
// or async/await
const results = await Promise.allSettled([promiseA(), promiseB(), promiseC()]);
results.forEach(result => console.log(result));
OUTPUT
{status: 'rejected', reason: 'promiseA something happened'}
{status: 'fulfilled', value: 'promiseB works!'}
{status: 'rejected', reason: 'promiseC something happened'}
The results
variable is an array ([]) that contains the results of each promise. The order of passed promises indicates the order of results.
promiseA => results[0] => {status: 'rejected', reason: 'promiseA something happened'}
promiseB => results[1] => {status: 'fulfilled', value: 'promiseB works!'}
promiseC => results[2] => {status: 'rejected', reason: 'promiseC something happened'}
Promise.allSettled()
NEVER rejects; the fulfillment value is an array of objects, each describing the outcome of one promise (status, value/reason).
Promise.allSettled – when to use?
Promise.allSettled() is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you’d always like to know the result of each promise. ~ [3] MDN Web Docs
Use Promise.allSettled() when you need to get results of many asynchronous methods at once, but it does not matter if all will be successful or not (promises are independent).
Promise.all vs Promise.allSettled
Promise.allSettled()
is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully ~ [3] MDN Web Docs
Promise.all()
may be more appropriate if the tasks are dependent on each other, or if you’d like to immediately reject upon any of them rejecting. ~ [3] MDN Web Docs
const promiseA = new Promise((resolve, reject) => setTimeout(() => resolve('promiseA works!'), 250));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve('promiseB works!'), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => reject('promiseC something happend'), 200));
Promise.all([promiseA(), promiseB(), promiseC()])
.then(results => {
results.forEach(result => console.log(result));
})
.catch(error => {
console.log(`Error: ${error}`);
});
Promise.allSettled([promiseA, promiseB, promiseC])
.then(results => {
results.forEach(result => console.log(result));
});
OUTPUT
// Promise.all
Error: promiseC something happened
// Promise.allSettled
{status: 'fulfilled', value: 'promiseA works!'}
{status: 'fulfilled', value: 'promiseB works!'}
{status: 'rejected', reason: 'promiseC something happened'}
Explanation
Promise.all()
will be rejected if any of the passed promises are rejected, such as promiseC.
Error: promiseC something happened
Promise.allSettled
will ALWAYS be fulfilled, and the catch block will NEVER be executed. Promise.allSettled
gives you a result of each promise independently with status
and reason
/value
.
{status: 'fulfilled', value: 'promiseA works!'}
{status: 'fulfilled', value: 'promiseB works!'}
{status: 'rejected', reason: 'promiseC something happened'}
Promise.any
The Promise.any() static method takes an iterable of promises as input and returns a single Promise. This returned promise fulfills when any of the input’s promises are fulfilled, with this first fulfillment value. ~ [3] MDN Web Docs
Example
const promiseA = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseA something happened!'
}), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseB works!'
}), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseC works!'
}), 250));
// Promise.any() [...] takes an iterable of promises as input
// it can be an array of promisses [promiseA, promiseB, promiseC];
// and returns a single Promise
// single promise can be resolved with then or async/await
// then
Promise.any([promiseA(), promiseB(), promiseC()])
.then(result => {
console.log(result);
})
.catch(error => {
console.log(`Error: ${error}`);
});
// or async/await
try {
const result = await Promise.any([promiseA(), promiseB(), promiseC()]);
console.log(result);
} catch (error) {
console.log(`Error: ${error}`);
}
OUTPUT
{message: 'promiseC works!'}
promiseA - 200 ms - rejected
promiseC - 250 ms - fulfilled <= first fulfillment value
promiseB - 300 ms - fulfilled
This returned promise fulfills when any of the input’s promises fulfills, with this first fulfillment value.
In our example, the first fulfillment value comes from promiseC
.
Important!
The promise returned by Promise.any()
fulfills any first fulfilled promise. Even if some promises get rejected, these rejections are ignored [5].
What will happen if ALL of the promises fail?
It rejects when all of the input’s promises are rejected (including when an empty iterable is passed), with an AggregateError containing an array of rejection reasons. ~ [2] MDN Web Docs
const promiseA = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseA something happens!'
}), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseB something happens!'
}), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseC something happens!'
}), 250));
Promise.any([promiseA(), promiseB(), promiseC()])
.then(result => {
console.log(result);
})
.catch(error => {
console.log(`Error: ${error}`);
});
OUTPUT
Error: AggregateError: All promises were rejected
Promise.any()
rejects when ALL of the passed promises are rejected.
Promise.any – when to use?
This method is useful for returning the first promise that fulfills. It short-circuits after a promise fulfills, so it does not wait for the other promises to complete once it finds one. ~ [3] MDN Web Docs
Assume that you have configuration stored in three different systems Salesforce, AWS, and Firebase. It’s important to you to load the page as soon as possible, so you call all three services but need only a response from the fastest one.
import { LightningElement } from "lwc";
import getConfigFromSalesforce from "@salesforce/apex/MyController.getConfigFromSalesforce";
import getConfigFromAWS from "@salesforce/apex/MyController.getConfigFromAWS";
import getConfigFromFirebase from "@salesforce/apex/MyController.getConfigFromFirebase";
export default class MyComponent extends LightningElement {
settings = {}
connectedCallback() {
Promise.any([
getConfigFromSalesforce(),
getConfigFromAWS(),
getConfigFromFirebase()
]).then(result => {
this.settings = result;
})
.catch(err => {
console.error(error);
});
}
}
Promise.race
The Promise.race() static method takes an iterable of promises as input and returns a single Promise. This returned promise settles with the eventual state of the first promise that settles.
Example
const promiseA = new Promise((resolve, reject) => setTimeout(() => reject({
message: 'promiseA something happened!'
}), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseB works!'
}), 300));
const promiseC = new Promise((resolve, reject) => setTimeout(() => resolve({
message: 'promiseC works!'
}), 250));
// Promise.race() [...] takes an iterable of promises as input
// it can be an array of promisses [promiseA, promiseB, promiseC];
// and returns a single Promise
// single promise can be resolved with then or async/await
// then
Promise.race([promiseA(), promiseB(), promiseC()])
.then(result => {
console.log(result);
})
.catch(error => {
console.log(`Error: ${JSON.stringify(error)}`);
});
// or async/await
try {
const result = await Promise.race([promiseA(), promiseB(), promiseC()]);
console.log(result);
} catch (error) {
console.log(`Error: ${JSON.stringify(error)}`);
}
OUTPUT
Error: {"message": "promiseA something happened!"}
The result
is the first fulfillment value. It contains a value of the method that will be the fastest. Promise.race()
is fulfilled if the fastest promise is fulfilled and rejected when the fastest promise is rejected.
Promise.race – when to use?
This method is useful for returning the first promise that fulfills. It short-circuits after a promise fulfills, so it does not wait for the other promises to complete once it finds one. ~ [3] MDN Web Docs
Assume that you have configuration stored in three different systems Salesforce, AWS, and Firebase. It’s important to you to load the page as soon as possible, so you call all three services but need only a response from the fastest one.
Promise.any vs Promise.race
Promise.any
and Promise.race
are quite similar, but do different things.
const promiseA = new Promise((resolve, reject) => setTimeout(() => reject('Oh something happened'), 200));
const promiseB = new Promise((resolve, reject) => setTimeout(() => resolve('It works!'), 300));
// Race vs Any - [promiseA, promiseB]
Promise.race([promiseA(), promiseB()])
.then(result => {
console.log(`race: success with: ${result}`);
})
.catch(error => {
console.log(`race: error with: ${error}`);
});
Promise.any([promiseA(), promiseB()])
.then(result => {
console.log(`any: success with: ${result}`);
})
.catch(error => {
console.log(`any: error with: ${error}`);
});
const promiseC = new Promise((resolve, reject) => setTimeout(() => reject('Oh something happend'), 400));
// Race vs Any - [promiseA, promiseC]
Promise.race([promiseA(), promiseC()])
.then(result => {
console.log(`race: success with: ${result}`);
})
.catch(error => {
console.log(`race: error with: ${error}`);
});
Promise.any([promiseA(), promiseC()])
.then(result => {
console.log(`any: success with: ${result}`);
})
.catch(error => {
console.log(`any: error with: ${error}`);
});
OUTPUT
Race vs Any - [promiseA, promiseB]
race: error with: Oh something happened
any: success with: It works!
Race vs Any - [promiseA, promiseC]
race: error with: Oh something happened
any: error with: AggregateError: All promises were rejected
promiseA => 200 ms => rejected
promiseB => 300 ms => fulfilled
promiseC => 400 ms => rejected
Explanation
Promise.race()
is settled as soon as any of the passed promises is SETTLED (fulfilled or rejected).
In our example, Promise.race([promiseA, promiseB])
, promiseA
is settled (rejected) faster than promiseB
.
The whole Promise.race()
will be rejected and the code in the catch block will be executed.
Promise.any()
is settled as soon as any of the passed promises is FULFILLED (success).
The only difference between Promise.race()
and Promise.any()
is that:
Promise.race()
waiting for the first promise that will be SETTLED (fulfilled or rejected).Promise.any()
waiting for the first promise that will be FULFILLED (success), and rejects only when ALL of the passed promises are rejected.
Summary
Method | Use Case | When Rejects |
---|---|---|
Promise.all | when multiple related promises must be completed at once and all need to be fulfilled (succeed) | when ANY of the passed promises is rejected |
Promise.allSettled | when multiple NOT related promises must be completed at once | NEVER |
Promise.any | when the result of the first promise that SETTLED (fulfilled or rejected) needed | when ANY of the passed promises is rejected |
Promise.race | when the result of the first promise that FULFILLS (succeed) needed | when ALL passed promises are rejected |
If you have any questions feel free to ask in the comment section below. 🙂
Was it helpful? Check out our other great posts here.