Re-study JS: async/await

Preface

Asynchronous operation has always been an indispensable part of JS. From the initial callback function to the later Proise, then to the async function introduced in ES2017, asynchronous operation has gradually evolved and become more and more simple and convenient. Next, we will carefully look at the changes of async operation after the introduction of async function in ES2017.

What's the usage?

In the past, we used async/await functions together, but this time I'm going to take apart and introduce the usefulness of async and await respectively.

async effect

Usually, the async command is used because there is an await command inside the function, because the await command can only appear in the async function, otherwise the grammar will be reported. That's why async/await pairs appear, but what happens if an async is added to a common function alone? Let's take an example:

async function test () {
  let a = 2
  return a
}
const res = test()
console.log(res)


The async function returns a Promise object from the example. If there is a return value in the function, async will encapsulate the return value into a Promise object through Promise.resole(). It is also very simple to get this value. It can be extracted directly through then(), for example:

res.then(a => {
  console.log(a) // 2
})

In the absence of await, calling the async function will immediately execute and return a Promise. What's the difference with await?

await effect

Generally speaking, the await command is followed by a Promise object, waiting for the state of the Promise object to change to get the return value, but it can also receive the return result of any expression. Let's take an example:

function a () {
  return 'a'
}
async function b () {
  return 'b'
}
const c = await a()
const d = await b()
console.log(c, d) // 'a' 'b'

From the example, we can see that no matter what expression we receive after await, we can wait for the result to return. When we are not a Promise object, we will wait for the result to return. When we wait for a Promise object, we will block the code behind, wait for the state of the Promise object to change, and get the corresponding value as await waiting. As a result, blocking here refers to blocking within async, and calls to async functions do not block.

What problems have been solved?

Promise...then grammar has solved the problem of multi-layer callback nesting that has existed before, so why use async/await? To answer this question, let's start with a Promise code:

function login () {
  return new Promise(resolve => {
    resolve('aaaa')
  })
}
function getUserInfo (token) {
  return new Promise(resolve => {
    if (token) {
      resolve({
        isVip: true
      })
    }
  })
}
function getVipGoods (userInfo) {
  return new Promise(resolve => {
    if (userInfo.isVip) {
      resolve({
        id: 'xxx',
        price: 'xxx'
      })
    }
  })
}
function showVipGoods (vipGoods) {
  console.log(vipGoods.id + '----' + vipGoods.price)
}
login()
  .then(token => getUserInfo(token))
  .then(userInfo => getVipGoods(userInfo))
  .then(vipGoods => showVipGoods(vipGoods))

As shown in the example, each Promise is equivalent to an asynchronous network request. Usually a business process requires multiple network requests, and network requests rely on one request result. The above example is Promise, which simulates this process. Let's see how async/await is different, for example:

async function call() {
  const token = await login()
  const userInfo = await getUserInfo(token)
  const vipGoods = await getVipGoods(userInfo)
  showVipGoods(vipGoods)
}
call()

async/await calls are clearer and simpler than the Promise call on the then chain, as is synchronized code.

What problems have been brought about?

With async/await, we often overlook a problem. The cumulative time of synchronous execution will slow down the program. Sometimes our code can be written and executed concurrently. But because async/await makes secondary execution, let's see an example:

function test () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test')
      resolve()
    }, 1000)
  })
}
function test1 () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test1')
      resolve()
    }, 1000)
  })
}
function test2 () {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log('test2')
      resolve()
    }, 1000)
  })
}
async function call () {
  await test()
  await test1()
  await test2()
}
call ()

The code above takes time to execute.


In fact, I don't care about the sequence of execution of this code. Subsequent execution wastes a lot of execution time. Let's change it to concurrent execution:

function call () {
  Promise.all([test(), test1(), test2()])
}
call()

Time spent:

Therefore, special attention should be paid to this when using async/await

Small Problems in Cycles

When writing JS loops, JS provides a lot of useful array api interfaces, forEach is one of them, but when you encounter async/await, it may be tragic. You get a result that is not what you want. Let's see an example:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
users.forEach(async user => {
  let info = await getUserInfo(user.id)
  userInfos.push(info)
})
console.log(userInfos) // []

Are you familiar with the above code? It simulates getting user information from multiple users and then gets an array of user information. Unfortunately, the user Infos above gets an empty array. After adding async/await to the above code, the forEach loop becomes asynchronous, so it won't wait for all users. Print userInfos after all requests for information. If you want to wait for the result to return to print, or go back to the old for loop to see the code:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
async function call() {
  for (user of users) {
    let info = await getUserInfo(user.id)
    userInfos.push(info)
  }
  console.log(userInfos)
}
call()


This is a secondary way of writing, that is to say, you will wait for the first task to finish and then execute the next one, but maybe you don't care about the execution process, as long as you get the desired results, then the concurrent way will be more efficient. Look at the code:

function getUserInfo (id) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        id: id,
        name: 'xxx',
        age: 'xxx'
      })
    }, 200)
  })
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
const promises = users.map(user => getUserInfo(user.id))
Promise.all(promises).then(res => {
  userInfos = res
  console.log(userInfos)
})


As you can see from the above example, concurrent execution is much more efficient.

summary

This article makes a brief summary of the usage of async/await and some of the problems we often encounter, hoping to be helpful when we use it.

If there are mistakes or inaccuracies, you are welcome to criticize and correct, if you like, you are welcome to praise.

Keywords: Javascript network

Added by Gregg on Sat, 17 Aug 2019 15:05:40 +0300