[TOC]
在新的异步编程规范出来之前,很容易写出多层嵌套的代码。
const main = (paramA, paramB, paramC, done) => {
funcA(paramA, (err, resA) => {
if (err) {
return done(err)
}
return funcB(paramB, (err, resB) => {
if (err) {
return done(err)
}
funcC(paramC, (err, resC) => {
if (err) {
return done(err)
}
return done(null, { resA, resB, resC })
})
})
})
}
Generator 函数是 ES6 提出的一种异步编程解决方案,可以将 Generator 函数理解为一个状态机,封装了多个内部状态。
将上面的代码改写成 Generator 的方式如下:
function funcA(param) {
return param + 1;
}
function funcB(param) {
return param + 2;
}
function funcC(param) {
return param + 3;
}
function* main(paramA, paramB, paramC) {
const resA = yield funcA(paramA);
const resB = yield funcB(paramB);
const resC = yield funcC(paramC);
}
const gen = main('1', '2', 3);
const iter = gen.next();
while (!iter.done) {
console.log(iter.value);
iter = gen.next();
}
可以看到,使用 Generator 生成器函数需要手动去调用 next()
方法,去执行相关的异步代码,还是不太方便。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便,async 函数可以看做 Generator 函数的语法糖。
上面的代码可以用 async/await 改写成:
function funcA(param) {
return Promise.resolve(param + 1);
}
function funcB(param) {
return Promise.resolve(param + 2);
}
function funcC(param) {
return Promise.resolve(param + 3);
}
const main = async (paramA, paramB, paramC) => {
const resA = await funcA(paramA);
const resB = await funcB(paramB);
const resC = await funcC(paramC);
return { resA, resB, resC };
}
main('1', '2', 3).then(console.log);
- 内置执行器,Generator 函数的执行必须靠执行器(例如
next()
方法,或者第三方co
模块) - 更好的语义
- 更广的适用性,yield 命令后边只能是 Thunk 函数或 Promise 对象,而 async 函数中 await 命令后边可以是 Promise 对象和原始类型的值
- 返回值是 Promise,而 Generator 函数执行后返回的是 Iterator 对象
你总是将生活想象的太美好,如果上边的函数出错了呢?谁来承担处理错误?
你可能会这么做,针对每一个可能的错误,使用 try/catch
来捕获。
const main = async (paramA, paramB, paramC) => {
let resA, resB, resC;
try {
resA = await funcA(paramA);
} catch (error) {
throw error;
}
try {
resB = await funcB(paramB);
} catch (error) {
throw error;
}
try {
resC = await funcC(paramC);
} catch (error) {
throw error;
}
return { resA, resB, resC };
}
但是,实际的情况是,有时候并不需要针对每一个错误单独处理,因此可以将可能发生错误的代码一网打尽,可以根据错误对象来处理。
const main = async (paramA, paramB, paramC) => {
try {
const resA = await funcA(paramA);
const resB = await funcB(paramB);
const resC = await funcC(paramC);
return { resA, resB, resC };
} catch (error) {
// you can analyze the structure of the error object
throw error;
}
}
最后,在 async 函数中抛出的异常,需要由调用 async 函数的宿主函数来负责捕获。
const main = async (paramA, paramB, paramC) => {
try {
const resA = await funcA(paramA);
const resB = await funcB(paramB);
const resC = await funcC(paramC);
return { resA, resB, resC };
} catch (error) {
// you can analyze the structure of the error object
throw error;
}
}
main('1', '2', 3)
.then(d => console.log('do something awesome with the result'))
.catch(e => console.log('handle that error!'));
不过,之前提到 async 函数返回的都是 Promise 对象,因此我们甚至都不需要在 async 中使用多此一举的 try/catch
。
// Since each of the await calls will trigger the `.catch` if they fail...
const main = async (paramsA, paramsB, paramsC) => {
const resA = await funcA(paramsA);
const resB = await funcB(paramsB);
const resC = await funcC(paramsC);
return { resA, resB, resC };
}
// ... all we need is this `.catch` to handle all of them.
main('1', '2', 3)
.then(d => console.log('do something awesome with the result'))
.catch(e => console.log('handle that error!'));
另外,可以将错误的单独处理和通用处理结合在一起,如下:
const main = async (paramsA, paramsB, paramsC) => {
const resA = await funcA(paramsA);
const resB = await funcB(paramsB).catch(e => console.log('things unique to this error'));
const resC = await funcC(paramsC);
return { resA, resB, resC };
}
// ... all we need is this `.catch` to handle all of them.
main('1', '2', 3)
.then(d => console.log('do something awesome with the result'))
.catch(e => console.log('handle that error!'));
如果一些异步函数之间没有依赖关系,可以使用 Promise.all()
来改善请求时间。
const main = async (paramsA, paramsB, paramsC) => {
const resA = await funcA(paramsA);
// if resB and recC have no dependency on each other
// they can be requested parallel
const [resB, resC] = await Promise.all([funcB(paramsB), funcC(paramsC)]);
return { resA, resB, resC };
}