异步流程控制
本文主要讲解 JavaScript 在异步流程控制中的一些实践、容错以及复杂异步环境下我们该如何去处理。
发展历史
异步流程控制的发展历史大概是 callback hell => Promise => Generator => async/await
。 ES6 中 Promise
是通过 .then().then().catch()
的方式来解决 callback
多层嵌套的问题。 但 Promise 依然是异步执行的,这时候 TJ 的 co,通过 Generator
实现了异步代码的同步化。这个模式和 ES7
中的 async/await
类似。
javascript
function A() {
// async get dataA
function B(dataA) {
// async get dataB
function C(dataB) {}
}
}
Promise(A)
.then(B)
.then(C)
.catch((err) => console.log(err))
co(function* () {
var dataA = yield A()
var dataB = yield B(dataA)
var dataC = yield C(dataB)
})
;async () => {
const dataA = await A()
const dataB = await B(dataA)
const dataC = await C(dataB)
}
Promise 实践和容错
之前当面试官的时候,如果面试对象经常使用 ES6
,我会喜欢问一个问题:
- 假设你的移动端页面上有头部、中部、底部三部分数据需要并发的去请求 api 拿到返回数据,你会怎么处理?
- 用 Promise 如何实现?
- 如果其中一个 API 出了错误怎么容错?
- 第一个问题很简单,依次执行三个异步请求函数,在获取到数据后执行渲染函数填充到页面上
- 第二个问题,其实也没多绕,你可以同时执行三个 Promise 函数,也可以打包成
Promise.all()
一并执行,显然对于这种并发执行的异步函数Promise.all()
更符合程序设计。
javascript
const render = (log = console.log)
const asyncApi = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof num !== 'number') {
reject('param error')
}
num += 10
resolve(num)
}, 100)
})
}
asyncApi(0).then(render).catch(log) // 10
asyncApi(5).then(render).catch(log) // 15
asyncApi(10).then(render).catch(log) // 20
Promise.all([asyncApi(0), asyncApi(5), asyncApi(10)])
.then(render)
.catch(log) // [ 10, 15, 20 ]
- 无论怎样,我会把面试者引导到
Promise.all()
上,这时候我会抛出问题 如果其中一个 API 出了错误怎么容错?
javascript
asyncApi(0).then(render).catch(log) // 10
asyncApi(false).then(render).catch(log) // param error
asyncApi(10).then(render).catch(log) // 20
Promise.all([asyncApi(0), asyncApi(false), asyncApi(10)])
.then(render)
.catch(log) // param error
对比发现,Promise
之间互不影响。但由于 Promise.all()
其实是将传入的多个 Promise
打包成一个,任何一个地方出错了都会直接抛出异常,导致不执行 then
直接跳到了 catch
,丢失了成功的数据。
- 解决方式是使用 resolve 传递错误,then 环节去处理
javascript
const render = (log = console.log)
const asyncApi = (num) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (typeof num !== 'number') {
resolve({ err: 'param error' }) // 修改前:reject('param error')
}
num += 10
resolve({ data: num }) // 修改前:resolve(num)
}, 100)
})
}
Promise.all([asyncApi(0), asyncApi(false), asyncApi(10)])
.then(render)
.catch(log)
// [ { data: 10 }, { err: 'param error' }, { data: 20 } ],这时候就可以区分处理了