【深入学习JS】13_async/await

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);
  • 两种写法进行合并,适用于GeneratorPromise或者回调函数的情况:
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 的实现原理

asyncawaitco库的官方实现。也可以看作自带启动器的generator函数的语法糖。也可以理解为async函数是基于 PromiseGenerator 的一层封装。

// 使用 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的语法糖,可以借助generatorpromise实现;
  • 每次给generator编写执行器太麻烦时可以使用coasync/awaitco库的官方实现;
  • async/await虽然可以将异步编程同步化,但是有时候顺序执行会影响请求速度,所以应该慎重。

我有一种我明天就会忘得差不多的感觉,好难啊。

参考


若有错误,欢迎指出,感谢~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值