必须知道的 Promise 进阶点(二)

306 阅读3分钟

好久没写博客了,今天终于把拖了好久的一篇写完了。

原文地址:必须知道的 Promise 进阶点(二)

前面还有:必须知道的 Promise 进阶点(一)

async/await 基础

比起回调函数那看不懂的嵌套,Promise 要清爽不少,但当任务较多时, Promise 也有可能会有比较长的链和嵌套,这时候使用 async/await 就会让代码易读很多。

async

async 函数可以看作是 Promise 的语法糖:

// resolve 状态
async function foo() {
    console.log('start')
    return 'resolve'
}
foo().then(data => {
    console.log('data:', data)
})
console.log('end')
// start
// end
// data: resolve


// reject状态
async function foo() {
    console.log('start')
    throw new Error('reject')
}
foo().catch(data => {
    console.log('data:', data.message)
})
console.log('end')
// start
// end
// data: reject

与下面直接使用 Promise 方式书写的效果一模一样:

// resolve 状态
function foo() {
    return new Promise((resolve, reject) => {
        console.log('start')
        resolve('resolve')
    })
}
foo().then(data => {
    console.log('data:', data)
})
console.log('end')
// start
// end
// data: resolve


// reject状态
function foo() {
    return new Promise((resolve, reject) => {
        console.log('start')
	reject('reject')
    })
}
foo().catch(data => {
    console.log('data:', data)
})
console.log('end')
// start
// end
// data: reject

await

await 与其字面意思一样——等待,等待其后的函数执行完毕。

// resolve状态
async function foo() {
    console.log('start')
    return 'resolve'
}
async function bar() {
    const data = await foo()
    console.log('data', data)
}
bar()
console.log('end')
// start
// end
// data resolve

// reject状态
async function foo() {
    console.log('start')
    throw new Error('reject')
}
async function bar() {
    try {
        const data = await foo()
        console.log('data', data)
    } catch (err) {
	console.log('data', err.message)
    }
}
bar()
console.log('end')
// start
// end
// data reject

await 只能在 async 函数里使用,否则会报错

循环中的 async/await

首先思考下面两个场景:

  1. 有一个异步请求,需要多次并且按顺序发送

  2. 有一个异步请求,需要多次发送,但不用按顺序

场景一

同一个请求,多次,按顺序。这就是一个典型的串行处理

function mockServer(i) {
    return new Promise((reslove, rejecy)=> {
        setTimeout(() => {
	    reslove('有值了:' + i)
        }, 1000 * i)
    })
}

async function getData(time) {
    var data = await mockServer(time)
    return data
}

var arr = [1,2,3,4]

async function showData() {
  console.time('showData')

  for (const item of arr) {
    const data = await getData(item)
    console.log(data)
  }
  console.timeEnd('showData')
}
showData()

// 有值了: 1
// 有值了: 2
// 有值了: 3
// 有值了: 4
// howData: 13100.510009765625ms

我们通过 for-of 循环调用了 4 次异步函数 getData,由于 getData 前面加了关键字 await,所以会依次排队处理,一共花了13秒多的时间。

场景二

同一个请求,多次,不按顺序。这就是一个典型的并行处理,每个请求同时发送,而不用排队等候,节约时间。

function mockServer(i) {
    return new Promise((reslove, rejecy)=> {
        setTimeout(() => {
	    reslove('有值了:', i)
        }, 1000 * i)
    })
}

async function getData(time) {
    var data = await mockServer(time)
    return data
}

var arr = [1,2,3,4]

async function showData() {
  console.time('showData')

  var allAsyncRequest = arr.map(item => getData(item))
  for await  (const asyncRequest of allAsyncRequest) {
    const data = asyncRequest
    console.log(data)
  }
  console.timeEnd('showData')
}
showData()

// 有值了: 1
// 有值了: 2
// 有值了: 3
// 有值了: 4
// showData: 4131.318115234375ms

我们在 map 的回调里调用了 4 次异步请求函数,将请求事件放到事件队列里面,让 4 个请求可以同时处理,而不影响后续任务的执行。

然后再通过 for await...of 来等待 4 个异步请求都执行完,一共花了 4 秒,大大节约了时间。

这里的 for await...of 还可以换一种写法:

for (const asyncRequest of allAsyncRequest) {
    const data = await asyncRequest
    console.log(data)
}

也可以使用 Promise.all()

Promise.all(allAsyncRequest).then((data) => {
    console.log(data)
    console.timeEnd('showData')
})

// ["有值了:1", "有值了:2", "有值了:3", "有值了:4"]
// showData: 4441.679931640625ms

注意:这里不能使用 forEach 来进行循环处理,具体原因可以看 当async/await遇上forEach,这篇文章已经写的很清楚了。

最后

学会熟练使用 async/await,可以很好提升代码的可阅读和可维护性,大家如果还有更好的用法和建议,欢迎在评论区补充。