Mastering Promises: 8 Advanced Techniques for JavaScript
Written on
Chapter 1: Understanding Promises in Depth
In JavaScript development, promises are a fundamental concept that should not be overlooked. Surprisingly, many mid-level and senior front-end developers rely heavily on basic methods like promiseInst.then(), promiseInst.catch(), Promise.all, and even async/await, often without a comprehensive understanding of their workings. This guide presents advanced techniques for using promises, many of which are actively employed in the ALOVA request strategy library. By the end of this, you should feel confident in tackling any related queries.
Section 1.1: Executing Promises Sequentially
To execute a series of API calls in order, one might instinctively consider using await. For instance:
const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
for (const requestItem of requestAry) {
await requestItem();
}
Alternatively, using promise chaining can achieve the same effect:
const requestAry = [() => api.request1(), () => api.request2(), () => api.request3()];
const finalPromise = requestAry.reduce(
(currentPromise, nextRequest) => currentPromise.then(() => nextRequest()),
Promise.resolve() // Start with a resolved promise
);
Section 1.2: Managing State Outside Promise Scope
Consider a scenario where you need to gather user information before utilizing certain functionalities. Different developers approach this in varying ways:
- Junior Developer: "I'll create a modal and copy it across pages for efficiency!"
- Intermediate Developer: "That's not maintainable; we should encapsulate this component and import it where needed."
- Senior Developer: "Let’s encapsulate everything that needs encapsulating! Writing it in a single place for universal access is far more efficient."
Here’s a Vue3 example from a Senior Developer demonstrating how to implement this effectively:
getInfoByModal(); // This method directly calls the modal to collect user data.
This showcases how commonly used components can be encapsulated within UI libraries.
Section 1.3: Alternative Uses of Async/Await
Many developers limit their use of await to receiving return values from "async functions," failing to recognize that an async function inherently returns a promise. For instance, the following functions are equivalent:
const fn1 = async () => 1;
const fn2 = () => Promise.resolve(1);
Both functions will yield a promise resolving to 1. Moreover, if await is followed by a non-promise value, it will convert this value into a promise object, ensuring the subsequent code executes asynchronously:
Promise.resolve().then(() => {
console.log(1);
});
await 2; // Executes asynchronously
console.log(2);
The output sequence will be: 1 2.
The first video, Efficient Meetings - 7 Tips To Run an Effective Meeting, offers insights into improving meeting productivity.
Section 1.4: Sharing Requests with Promises
To avoid redundant requests when a response is pending, we can share the response from the first request with subsequent ones. For example:
request('GET', '/test-api').then(response1 => {
// Handle response
});
request('GET', '/test-api').then(response2 => {
// Handle response
});
In this case, both requests will be sent once, sharing the same response. This technique is beneficial in scenarios such as:
- Rendering multiple components that fetch data simultaneously.
- Users clicking the submit button multiple times before disabling it.
- Preloading data before transitioning to the relevant page.
This is one of Alova's advanced features, which incorporates promise caching for managing requests. The implementation could look like this:
const pendingPromises = {};
function request(type, url, data) {
const requestKey = JSON.stringify([type, url, data]);
if (pendingPromises[requestKey]) {
return pendingPromises[requestKey];}
const fetchPromise = fetch(url, {
method: type,
body: JSON.stringify(data)
})
.then(response => response.json())
.finally(() => {
delete pendingPromises[requestKey];});
return pendingPromises[requestKey] = fetchPromise;
}
Section 1.5: Understanding Promise States
It's essential to know that promises can only transition from "pending" to either "fulfilled" or "rejected." Consider the following example:
const promise = new Promise((resolve, reject) => {
resolve();
reject(); // This call is ignored; the promise remains fulfilled.
});
Thus, once a promise is fulfilled, it cannot revert to a pending state.
Section 1.6: Clarifying then, catch, and finally
In summary, all three functions return a new promise object. If an error occurs, it transitions to a rejected state. Here’s a concise example to illustrate their behavior:
// then function
Promise.resolve().then(() => 1); // Returns a promise resolved with 1
Promise.reject().then(() => 2).catch(() => 3); // Returns 3
Section 1.7: Differences in Error Handling
While both the second callback of then and catch handle errors, the former cannot catch errors thrown in the first callback:
Promise.resolve().then(
() => {
throw new Error('Error from successful callback');},
() => {
// This won't execute}
).catch(reason => {
console.log(reason.message); // Outputs: "Error from successful callback"
});
Section 1.8: Implementing Promises in Koa2 Middleware
Koa2 utilizes an onion model for processing requests, allowing for layered handling of middleware. Here’s a simple implementation:
const app = new Koa();
app.use(async (ctx, next) => {
console.log('Layer A Start');
await next();
console.log('Layer A End');
});
app.use(async (ctx, next) => {
console.log('Layer B Start');
await next();
console.log('Layer B End');
});
app.listen(3000);
The output sequence will be: Layer A Start -> Layer B Start -> Layer B End -> Layer A End.
The second video, How to have more effective and productive meetings, provides strategies for improving meeting efficiency.