async/await与Generator的相似之处
上一节了解了Generator
,在最后的时候介绍了异步编程同步化的思想,其中有这么一个栗子:
function* gen(){
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
我们再来看看如果用async/await
的使用方式:
function getData(){
let r1 = await fetchData('https://api.github.com/users/github');
var r2 = await fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
有没有觉得很相似,但还是有些不同的,对于generator
,我们需要手动调用next()
才能将给yeild
赋值。如果像上一节举的例子中,给generator
搭配使用一个执行器函数,就能实现类似asycn/await
的功能了。
编写执行器
- 当
Generator
中使用的Promise
时:
var fetch = require('node-fetch');
function* gen(){
var r1 = yield fetch('https://api.github.com/users/github');
var r2 = yield fetch('https://api.github.com/users/github/followers');
var r3 = yield fetch('https://api.github.com/users/github/repos');
console.log([r1.bio, r2[0].login, r3[0].full_name].join('\n'));
}
function run(){
var g = gen();
function next(data){
var result = g.next(data);
if(result.done)return;
result.value.then(function(data){
return data.json();
}).then(function(data){
next(data);
})
}
next();
}
run(gen);
- 当
Generator
中使用的回调函数时:
function fetchData(url){
return function(cb){
setTimeout(function(){
cb({status: 200, data: url});
}, 1000)
}
}
function* gen(){
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
function run(gen){
var g = gen();
function next(data){
var result = g.next(data);
if(result.done)return;
result.value(next);
}
next();
}
run(gen);
- 两种写法进行合并,适用于
Generator
为Promise
或者回调函数的情况:
function run(gen){
var g = gen();
function next(data){
var result = gen.next(data);
if(result.done) return;
if(isPromise(result.value)){
result.value.then(function(data){
next(data);
})
}else {
result.value(next);
}
}
next();
}
function isPromise(obj){
return 'function' === typeof obj.then;
}
module.exports = run;
- 让启动器返回一个
Promise
,便于获得Generator
函数的返回值,或者捕获错误:
function run(gen){
var g = gen();
return new Promise(function(resolve, reject){
function next(data){
try {
var result = gen.next(data);
} catch(e){
return reject(e);
}
if(result.done){
return resolve(result.value);
}
var value = toPromise(result.value);
value.then(function(data){
next(data);
}, function(e){
reject(e);
})
}
next();
})
}
function isPromise(obj){
return 'function' === typeof obj.then;
}
function toPromise(obj){
if(isPromise(obj)) return obj;
if('functon' === typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkPromise(fn){
return new Promise(function(resolve, reject){
fn
})
}
module.exports = run;
- 优化代码:
function run(gen){
return new Promise(function(resolve, reject){
if(typeof gen == 'function') gen = gen();
if(!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
function onFulfilled(res){
var r;
try{
r = gen.next(res);
} catch(e){
return reject(e);
}
next(r);
}
function onRejected(err){
var r;
try{
r = gen.throw(err);
}catch(e){
return reject(e);
}
next(r);
}
function next(r){
if(r.done) return resolve(r.value);
var value = toPromise(r.value);
if(value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise ' +
'but the following object was passed: "' + String(r.value) + '"'))
}
})
}
function isPromise(obj) {
return 'function' == typeof obj.then;
}
function toPromise(obj) {
if (isPromise(obj)) return obj;
if ('function' == typeof obj) return thunkToPromise(obj);
return obj;
}
function thunkToPromise(fn) {
return new Promise(function(resolve, reject) {
fn(function(err, res) {
if (err) return reject(err);
resolve(res);
});
});
}
module.exports = run;
执行器的实现大致就是这样了。
Co
每次执行 generator 函数时自己写启动器比较麻烦。co函数库 是一个 generator 函数的自启动执行器,使用条件是 generator 函数的 yield 命令后面,只能是 thunk 函数或 Promise 对象,co 函数执行完返回一个 Promise 对象。
如果我们再将这个启动器函数写的完善一些,我们就相当于写了一个 co,实际上,上面的代码确实是来自于 co……
如果直接使用 co 模块,这两种不同的例子可以简写为:
// yield 后是一个 Promise
var fetch = require('node-fetch');
var co = require('co');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
var r2 = yield fetch('https://api.github.com/users/github/followers');
var json2 = yield r2.json();
var r3 = yield fetch('https://api.github.com/users/github/repos');
var json3 = yield r3.json();
console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n'));
}
co(gen);
// yield 后是一个回调函数
var co = require('co');
function fetchData(url) {
return function(cb) {
setTimeout(function() {
cb(null, { status: 200, data: url })
}, 1000)
}
}
function* gen() {
var r1 = yield fetchData('https://api.github.com/users/github');
var r2 = yield fetchData('https://api.github.com/users/github/followers');
console.log([r1.data, r2.data].join('\n'));
}
co(gen);
async/await 的实现原理
async
、await
是co
库的官方实现。也可以看作自带启动器的generator
函数的语法糖。也可以理解为async
函数是基于 Promise
和 Generator
的一层封装。
// 使用 generator
var fetch = require('node-fetch');
var co = require('co');
function* gen() {
var r1 = yield fetch('https://api.github.com/users/github');
var json1 = yield r1.json();
console.log(json1.bio);
}
co(gen);
// 使用 async
var fetch = require('node-fetch');
var fetchData = async function () {
var r1 = await fetch('https://api.github.com/users/github');
var json1 = await r1.json();
console.log(json1.bio);
};
fetchData();
优雅使用async/await
async地狱
// bad
// 两个毫无关系的数据请求由于使用了async/await,
// 导致getAnotherList必须在getList才能请求,影响了请求时间
async function test(){
const listData = await getList();
const anotherListData = await getAnotherList();
}
// good
async function test(){
const listPromise = getList();
const anotherListPromise = getAnotherList();
await listPromise;
await anotherListPromise;
}
// good
async function test(){
Promise.all([getList(), getAnotherList()]).then(...);
}
错误捕获
// bad
// 给每个async/await使用try catch捕获错误,会导致代码杂乱
async function test(cb){
try {
const listData = await getList();
if(!listData) return return cb('No listData found');
}catch(e){
return cb('Unexpected error occurred');
}
try {
const anotherListData = await getAnotherList();
if(!listData) return return cb('No anotherListData found');
}catch(e){
return cb('Unexpected error occurred');
}
}
// good
// 封装一个to函数
export default function to(promise){
return promise.then(data => {
return [null, data];
}).catch(err => [err]);
}
import to from './to.js';
async function test(cb){
let err, listData, anotherListData;
[err, listData] = await to(getList);
if(!listData) throw new CustomerError('No listData found');
[err, anotherListData] = await to(getAnotherList);
if(!anotherListData) throw new CustomerError('No getAnotherList found');
}
总结
async/await
本质是generator
的语法糖,可以借助generator
和promise
实现;- 每次给
generator
编写执行器太麻烦时可以使用co
,async/await
是co
库的官方实现; async/await
虽然可以将异步编程同步化,但是有时候顺序执行会影响请求速度,所以应该慎重。
我有一种我明天就会忘得差不多的感觉,好难啊。
参考
若有错误,欢迎指出,感谢~