In May 2018, the 4.x version of Node has been stopped for maintenance, and a service of our company has been switched to 8.x. Currently, we are migrating koA2.x and replacing all the previous generator with Async. However, in the process of replacement, After finding some time waste caused by async abuse, let’s talk about how to optimize async code and make full use of asynchronous event stream to prevent async abuse
First, you need to know Promise
Promise is the basis for using async/await, so make sure you understand what promises do first. Promises are a great way to help with callback hell and make asynchronous processes clearer. A simple error-first-callback conversion to Promise example:
const fs = require('fs')
function readFile (fileName) {
return new Promise((resolve, reject) = > {
fs.readFile(fileName, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
readFile('test.log').then(data= > {
console.log('get data')
}, err => {
console.error(err)
})
Copy the code
We call a function to return an instance of Promise, and read the file during the instantiation process. When the file read callback is triggered, we change the Promise state, resolved state or Rejected state. We use then to listen and the first callback is resolve. The second callback is a reject processing.
Async relationship with Promise
Async functions are equivalent to a shorthand function that returns a Promise instance, with the following effect:
function getNumber () {
return new Promise((resolve, reject) = > {
resolve(1)})}/ / = >
async function getNumber () {
return 1
}
Copy the code
Both can be used in exactly the same way, using then to listen for the return value after calling getNumber. And the use of await syntax corresponding to async:
getNumber().then(data= > {
// got data
})
/ / = >
let data = await getNumber()
Copy the code
The execution of await gets the result of the Promise execution following the expression, just as we get the callback result by calling THEN. P.S. when async/await support is not very high, people will choose to use generator/yield combined with some libraries similar to CO to achieve similar effects
Async code is executed synchronously and results are returned asynchronously
It is important that async always returns an instance of a Promise, so when an async function is called, it means that the code inside the async function is in a new Promise, so it is executed synchronously, and the last return operation is equivalent to calling resolve in the Promise:
async function getNumber () {
console.log('call getNumber()')
return 1
}
getNumber().then(_= > console.log('resolved'))
console.log('done')
// Output sequence:
// call getNumber()
// done
// resolved
Copy the code
Promises within promises are digested
That is, if we have code like this:
function getNumber () {
return new Promise(resolve= > {
resolve(Promise.resolve(1))
})
}
getNumber().then(data= > console.log(data)) / / 1
Copy the code
The data we get in THEN should be the value passed in to Resolve, which is another instance of a Promise. But in practice, we get the return value: 1 directly, which means that if we return a Promise in a Promise, the program actually implements the Promise for us and triggers a callback like then when the internal Promise state changes. An interesting thing:
function getNumber () {
return new Promise(resolve= > {
resolve(Promise.reject(new Error('Test')))
})
}
getNumber().catch(err= > console.error(err)) // Error: Test
Copy the code
If we pass a reject in resolve, we can externally listen directly with a catch. This is often used to throw an exception in an async function:
async function getNumber () {
return Promise.reject(new Error('Test'))}try {
let number = await getNumber()
} catch (e) {
console.error(e)
}
Copy the code
Don’t forget the await keyword
There is no error at the code level if we forget to add the await keyword, but the return value we receive is a Promise
let number = getNumber()
console.log(number) // Promise
Copy the code
So remember the await keyword when you use it
let number = await getNumber()
console.log(number) / / 1
Copy the code
Not all places need to add await
At some point during code execution, not all asynchronies need to add await. For example, we assume that all apis of FS have been converted to the Promise version
async function writeFile () {
let fd = await fs.open('test.log')
fs.write(fd, 'hello')
fs.write(fd, 'world')
return fs.close(fd)
}
Copy the code
As mentioned above, the Promise inside the Promise will be digested, so we don’t use await in the last close. We open a file with await, and then write the file twice. Note, however, that we did not add the await keyword before the two writes to the file. Because that’s redundant, we just need to tell the API that I’m going to write a line of text into this file, and the order will be controlled by FS. Close is done last, because the callback to close will not fire if the write has not been completed. This means that the write has been completed.
Merges multiple unrelated async function calls
Now if we want to get a user’s profile picture and the user’s details (and these are two interfaces that don’t usually happen)
async function getUser () {
let avatar = await getAvatar()
let userInfo = await getUserInfo()
return {
avatar,
userInfo
}
}
Copy the code
One problem with this code is that our interface for retrieving user information does not depend on the return value of the avatar interface. However, such code will not send a request for user information until the avatar is obtained. So we can do this with code like this:
async function getUser () {
let [avatar, userInfo] = await Promise.all([getAvatar(), getUserInfo()])
return {
avatar,
userInfo
}
}
Copy the code
This change would make getAvatar execute simultaneously with the code inside getUserInfo, sending two requests at the same time, and making sure both return results through a layer of packets promise.all in the outer layer.
Let asynchronous functions that do not depend on each other execute simultaneously
Some considerations in the loop
forEach
When we call code like this:
async function getUsersInfo () {[1.2.3].forEach(async uid => {
console.log(await getUserInfo(uid))
})
}
function getuserInfo (uid) {
return new Promise(resolve= > {
setTimeout(_= > resolve(uid), 1000)})}await getUsersInfo()
Copy the code
There seems to be no problem with this execution, we will get 1, 2, 3 log output, but if we add console.log(‘done’) under await getUsersInfo(), we will find: We get done first and then three uid logs, which means that when getUsersInfo returns the result, the internal Promise isn’t finished. This is because forEach doesn’t care what the return value of the callback is, it just runs the callback.
Do not use await in normal for, while loops
Using normal for, while loops causes the program to become serial:
for (let uid of [1.2.3]) {
let result = await getUserInfo(uid)
}
Copy the code
This code will not request uid: 2 until it receives the data of UID: 1
Solutions to these two problems:
At present, it is optimal to replace it with map and Promise. All:
await Promise.all([1.2.3].map(async uid => await getUserInfo(uid)))
Copy the code
Such a code implementation instantiates three Promises simultaneously and requests getUserInfo
There is one in the P.S. draftawait*
, can be omittedPromise.all
await* [1.2.3].map(async uid => await getUserInfo(uid))
Copy the code
Why is P.S. in useGenerator
+co
There is no such problem
When using koa1.x, we simply yield []. Map without the serializing problems mentioned above:
function toPromise(obj) {
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
return obj;
}
function arrayToPromise(obj) {
return Promise.all(obj.map(toPromise, this));
}
Copy the code
Co is the one that helped us add the promise.all processing (bow to TJ).
conclusion
To summarize a few tips on async function writing:
- use
return Promise.reject()
inasync
The function throws an exception - Let asynchronous functions that have no dependencies on each other execute simultaneously
- Do not use/in loop callbacks
for
,while
Use in circulationawait
withmap
To replace it
The resources
- async-function-tips