By Adrian Mejia
Like it and see. Make it a habit
In this paper,
GitHub
Github.com/qq449245884…Has included more categories of previous articles, as well as a lot of my documentation and tutorial material. Welcome Star and Perfect, you can refer to the examination points for review in the interview, I hope we can have something together.
This article is a comprehensive tutorial on JavaScript Promises. It introduces necessary methods such as THEN, Catch, and finally. It also includes handling more complex cases, such as executing promises in parallel with promise.all, handling request timeouts through promise.race, Promise chains, and some best practices and common pitfalls.
1.JavaScript Promises
Promise is an object that allows us to handle asynchronous operations as an alternative to earlier es5 callbacks.
Promises have many advantages over callbacks, such as:
- Make asynchronous code easier to read.
- Provides composite error handling.
* Better process control allows asynchronous parallel or serial execution.
Callbacks are more likely to form a deeply nested structure (also known as callback hell). As follows:
a(() => {
b(() => {
c(() => {
d(() => {
// and so on ...
});
});
});
});
Copy the code
If you transform these functions into promises, you can link them together to produce more maintainable code. Like this:
Promise.resolve()
.then(a)
.then(b)
.then(c)
.then(d)
.catch(console.error);
Copy the code
In the example above, the Promise object exposes the.then and.catch methods, which we’ll explore later.
1.1 How do I convert an existing callback API into a Promise?
We can use the Promise constructor to convert the callback into a Promise.
The Promise constructor accepts a callback that takes resolve and reject.
- Resolve: is the callback that should be invoked when the asynchronous operation completes.
- Reject: Callback function to call when an error occurs.
The constructor immediately returns an object, the Promise instance. When you use the. Then method in a Promise instance, you can be notified when the promise “completes.” Let’s look at an example.
Promise is just a callback?
And it isn’t. Promises are more than just callbacks, but they do use asynchronous callbacks for the.then and.catch methods. Promises are an abstraction on top of callbacks, allowing us to link multiple asynchronous operations and handle errors more elegantly. Let’s see it in action.
Promise: Promises hell
a(() => {
b(() => {
c(() => {
d(() => {
// and so on ...
});
});
});
});
Copy the code
Do not convert the above callback to the following Promise form:
(a). Then (() = > {return b (). Then (() = > {return c (). Then (() = > {return d (). Then (() = > {/ / ⚠ ️ both Please never (do to this! ⚠ ️}); }); }); });Copy the code
The top turns into a Promise hell. Don’t do this. Instead, it’s better to do this:
a()
.then(b)
.then(c)
.then(d)
Copy the code
timeout
What do you think the output of the following program is?
Const promise = new promise ((resolve, reject) => {setTimeout(() => {resolve('time is up ⏰'); }, 1e3); SetTimeout (() => {reject('Oops 🔥'); }, 2e3); }); promise .then(console.log) .catch(console.error);Copy the code
Is the output:
Time is up ⏰ Oops! 🔥Copy the code
Or output:
Time is up ⏰Copy the code
The latter, because when a Promise resolved, it can no longer be rejected.
Once you call one method (resolve or Reject), the other method is invalidated because the promise is in a stable state. Let’s explore all the different states of a promise.
1.2 Promise state
Promise can be divided into four states:
- ⏳ Pending: indicates the initial status. The asynchronous operation is still in progress.
- ✅ depressing: The operation succeeds and it is called
.then
Callbacks, for example.then(onSuccess)
. - ⛔️ Rejected: The operation fails and it calls
.catch
or.then
Is the second argument, if any. For example,.catch(onError)
or.then(... , onError)
. - 😵 Settled: This is the final state of the promise. Promise was dead, and there was no other way to settle or reject it.
.finally
Method is called.
1.3 Promise instance method
The Promise API exposes three main methods: then, Catch, and finally. Let’s discuss one by one with examples.
Promise then
The then method lets you be notified when an asynchronous operation succeeds or fails. It contains two parameters, one for successful execution and one for error.
promise.then(onSuccess, onError);
Copy the code
You can also use catch to handle errors:
promise.then(onSuccess).catch(onError);
Copy the code
The chain of Promise
Then returns a new Promise so that multiple promises can be linked together. As in the following example:
Promise.resolve()
.then(() => console.log('then#1'))
.then(() => console.log('then#2'))
.then(() => console.log('then#3'));
Copy the code
Resolve immediately treat promises as success. Therefore, all of the following will be called. The output will be
then#1
then#2
then#3
Copy the code
Promise catch
The promise.catch method handles errors as arguments to functions. If nothing goes wrong, the catch method is never called.
Suppose we have the following promise: parse or reject them after 1 second and print out their letters.
const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3)); const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 1e3)); const c = () => new Promise((resolve, reject) => setTimeout(() => { console.log('c'), reject('Oops! ') }, 1e3)); const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 1e3));Copy the code
Note that C uses reject(‘Oops! ‘) simulates rejection.
Promise.resolve()
.then(a)
.then(b)
.then(c)
.then(d)
.catch(console.error)
Copy the code
The output is as follows:
In this case, you can see error messages on A, B, and C.
We can use the second argument to the then function to handle errors. Note, however, that catch is no longer executed.
Promise.resolve()
.then(a)
.then(b)
.then(c)
.then(d, () => console.log('c errored out but no big deal'))
.catch(console.error)
Copy the code
As we are dealing with. , onError), so no catch is called. D will not be called. If you want to ignore the error and continue with the Promise chain, you can add a catch to C. Like this:
Promise.resolve()
.then(a)
.then(b)
.then(() => c().catch(() => console.log('error ignored')))
.then(d)
.catch(console.error)
Copy the code
Of course, catching errors too early is not a good idea because it is easy to overlook potential problems during debugging.
Promise finally
The finally method is called only if the Promise state is Settled.
If you want a piece of code that always needs to be executed even if an error occurs, you can use.then after.catch.
Promise.resolve()
.then(a)
.then(b)
.then(c)
.then(d)
.catch(console.error)
.then(() => console.log('always called'));
Copy the code
Or you can use the.finally keyword:
Promise.resolve()
.then(a)
.then(b)
.then(c)
.then(d)
.catch(console.error)
.finally(() => console.log('always called'));
Copy the code
1.4 Promise class methods
We can directly use the four static methods in the Promise object.
- Promise.all
- Promise.reject
- Promise.resolve
- Promise.race
Promise. Resolve and Promise. Reject
These are helper functions that allow Promise to resolve or reject immediately. It is possible to pass an argument as the next receipt of.then:
Promise.resolve('Yay!!! ') .then(console.log) .catch(console.error)Copy the code
Yay!!
Promise. Reject (' Oops 🔥 '). Then (the console. The log). The catch (the console. The error)Copy the code
usePromise.all
Execute multiple promises in parallel
Typically, promises are executed one after the other, but you can use them in parallel.
Suppose you poll data from two different apis. If they are unrelated, we can use promise.all () to trigger both requests.
In this example, where the main function is to convert dollars to euros, we have two separate API calls. One is used for BTC/USD and the other is used to obtain EUR/USD. As you might expect, both API calls can be called in parallel. However, we need a way to know when to complete the final price calculation at the same time. We can use promise.all, which is typically used after starting multiple asynchronous tasks to run concurrently and creating promises for their results, so that people can wait for all tasks to complete.
const axios = require('axios'); const bitcoinPromise = axios.get('https://api.coinpaprika.com/v1/coins/btc-bitcoin/markets'); const dollarPromise = axios.get('https://api.exchangeratesapi.io/latest? base=USD'); const currency = 'EUR'; // Get the price of bitcoins on Promise.all([bitcoinPromise, dollarPromise]) .then(([bitcoinMarkets, dollarExchanges]) => { const byCoinbaseBtc = d => d.exchange_id === 'coinbase-pro' && d.pair === 'BTC/USD'; const coinbaseBtc = bitcoinMarkets.data.find(byCoinbaseBtc) const coinbaseBtcInUsd = coinbaseBtc.quotes.USD.price; const rate = dollarExchanges.data.rates[currency]; return rate * coinbaseBtcInUsd; }) .then(price => console.log(`The Bitcoin in ${currency} is ${price.toLocaleString()}`)) .catch(console.log);Copy the code
As you can see, Promise. All accepted a series of Promises. When both requests are complete, we can calculate the price.
Here’s another example:
const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));
console.time('promise.all');
Promise.all([a(), b(), c(), d()])
.then(results => console.log(`Done! ${results}`))
.catch(console.error)
.finally(() => console.timeEnd('promise.all'));
Copy the code
How long does it take to resolve these promises? 5 seconds? 1 second? Or two seconds?
I’ll leave that to you to check.
Promise race
The promise.race (iterable) method returns a Promise that is resolved or rejected once a Promise in the iterator is resolved or rejected.
const a = () => new Promise((resolve) => setTimeout(() => resolve('a'), 2000));
const b = () => new Promise((resolve) => setTimeout(() => resolve('b'), 1000));
const c = () => new Promise((resolve) => setTimeout(() => resolve('c'), 1000));
const d = () => new Promise((resolve) => setTimeout(() => resolve('d'), 1000));
console.time('promise.race');
Promise.race([a(), b(), c(), d()])
.then(results => console.log(`Done! ${results}`))
.catch(console.error)
.finally(() => console.timeEnd('promise.race'));
Copy the code
What is the output?
The output b. With promise.race, the first execution to complete will result in the last result returned.
You may ask: What is promise.Race used for?
I don’t use it very often. However, there are situations where it can come in handy, such as timing requests or batching an array of requests.
Promise.race([
fetch('http://slowwly.robertomurray.co.uk/delay/3000/url/https://api.jsonbin.io/b/5d1fb4dd138da811182c69af'),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('request timeout')), 1000))
])
.then(console.log)
.catch(console.error);
Copy the code
If the request is fast enough, the result of the request will be obtained.
1.5 Promise FAQ
The promise is executed sequentially and the parameters are passed
This time, we will use promises API for Node FS and connect two files:
const fs = require('fs').promises; // requires node v8+
fs.readFile('file.txt', 'utf8')
.then(content1 => fs.writeFile('output.txt', content1))
.then(() => fs.readFile('file2.txt', 'utf8'))
.then(content2 => fs.writeFile('output.txt', content2, { flag: 'a+' }))
.catch(error => console.log(error));
Copy the code
In this example, we read file 1 and write it to the Output file. Later, we read file 2 and attach it to the output file again. As you can see, the writeFile Promise returns the contents of the file, which you can use in the next THEN clause.
How to link multiple conditional commitments?
You may want to skip specific steps on the Promise chain. There are two ways to do this.
const a = () => new Promise((resolve) => setTimeout(() => { console.log('a'), resolve() }, 1e3));
const b = () => new Promise((resolve) => setTimeout(() => { console.log('b'), resolve() }, 2e3));
const c = () => new Promise((resolve) => setTimeout(() => { console.log('c'), resolve() }, 3e3));
const d = () => new Promise((resolve) => setTimeout(() => { console.log('d'), resolve() }, 4e3));
const shouldExecA = true;
const shouldExecB = false;
const shouldExecC = false;
const shouldExecD = true;
Promise.resolve()
.then(() => shouldExecA && a())
.then(() => shouldExecB && b())
.then(() => shouldExecC && c())
.then(() => shouldExecD && d())
.then(() => console.log('done'))
Copy the code
If you run the code example, you’ll notice that only A and D execute as expected.
Another approach is to create a chain and then add them only when:
const chain = Promise.resolve();
if (shouldExecA) chain = chain.then(a);
if (shouldExecB) chain = chain.then(b);
if (shouldExecC) chain = chain.then(c);
if (shouldExecD) chain = chain.then(d);
chain
.then(() => console.log('done'));
Copy the code
How do I limit parallel promises?
To do this, we need to limit promise.all in some way.
Suppose you have a number of concurrent requests to execute. It is not good to use promise.all (especially if the API is rate-constrained). So, we need a way to limit the number of promises, which we call promiseAllThrottled.
// simulate 10 async tasks that takes 5 seconds to complete.
const requests = Array(10)
.fill()
.map((_, i) => () => new Promise((resolve => setTimeout(() => { console.log(`exec'ing task #${i}`), resolve(`task #${i}`); }, 5000))));
promiseAllThrottled(requests, { concurrency: 3 })
.then(console.log)
.catch(error => console.error('Oops something went wrong', error));
Copy the code
The output should look like this:
The code above limits concurrency to three tasks executed in parallel.
One way to implement promiseAllThrottled is to use promise.race to limit the number of active tasks at a given time.
/**
* Similar to Promise.all but a concurrency limit
*
* @param {Array} iterable Array of functions that returns a promise
* @param {Object} concurrency max number of parallel promises running
*/
function promiseAllThrottled(iterable, { concurrency = 3 } = {}) {
const promises = [];
function enqueue(current = 0, queue = []) {
// return if done
if (current === iterable.length) { return Promise.resolve(); }
// take one promise from collection
const promise = iterable[current];
const activatedPromise = promise();
// add promise to the final result array
promises.push(activatedPromise);
// add current activated promise to queue and remove it when done
const autoRemovePromise = activatedPromise.then(() => {
// remove promise from the queue when done
return queue.splice(queue.indexOf(autoRemovePromise), 1);
});
// add promise to the queue
queue.push(autoRemovePromise);
// if queue length >= concurrency, wait for one promise to finish before adding more.
const readyForMore = queue.length < concurrency ? Promise.resolve() : Promise.race(queue);
return readyForMore.then(() => enqueue(current + 1, queue));
}
return enqueue()
.then(() => Promise.all(promises));
}
Copy the code
PromiseAllThrottled handles Promises one on one. It implements Promises and adds them to the queue. If the queue is below the concurrency limit, it continues to be added to the queue. Once the limit is reached, we use promise.race to wait for a Promise to complete, so it can be replaced with a new Promise. The trick here is that promises are automatically deleted from the queue once they are completed. In addition, we use Race to detect when promises are completed and to add new promises.
Talented people’s [three] is the biggest power of wisdom to share, if there is any mistake or suggestion in this blog, welcome talented people to leave a comment, finally, thank you for watching.
The bugs that may exist after code deployment cannot be known in real time. In order to solve these bugs, I spent a lot of time on log debugging. Incidentally, I recommend a good BUG monitoring tool for youFundebug.
Original text: adrianmejia.com/promises-tu…
This article continues to update every week, you can wechat search “big move the world” the first time to read, reply [welfare] there are many front-end video waiting for you, this article GitHub github.com/qq449245884… Already included, welcome Star.