VueBloghyhero6

关于 async await

2019-08-31 / 2019-09-03 / 328次浏览

阮老师曾经在2015年的时候,就对这个方法做好详细介绍,现在2019年了,咱们再来详细的学习下。

从最早期的回调函数,到 Promise 对象, 再到 Generator 函数,每次都有所改进。
``

异步I/O不就是读取一个文件,干嘛搞的这么复杂,

然而异步编程的最高境界,就是不关心它是不是异步。

async 函数就是隧道尽头之光

要跑这段脚本也容易,去node上进行调试就OK了。

var fs = require('fs');

var readFile = function (fileName){
    return new Promise(function (resolve, reject){
        fs.readFile(fileName, function(error, data){
            if (error) reject(error);
            resolve(data);
            });
        });
    };

var gen = function* (){
    var f1 = yield readFile('/etc/fstab');
    var f2 = yield readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
    };
var asyncReadFile = async function (){
    var f1 = await readFile('/etc/fstab');
    var f2 = await readFile('/etc/shells');
    console.log(f1.toString());
    console.log(f2.toString());
}; 

比较后会发现,async 函数就是将 Generator 函数的星号(*)替换成 async,将 yield 替换成 await,仅此而已。

async 函数对 Generator 函数的改进,提现在以下三点。

  1. 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库, 而 async 函数自带执行器。也就是说,async 函数的执行, 与普通函数一模一样,只要一行。

var result = asyncReadFile();

  1. 更好的语义。async 和 await, 比起 星号 和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

3.更广的适用性。co 函数库约定,yield 命令后面只能是 Thunk 函数 或 Promise 对象,而 async 函数的 await命令后面,可以跟
Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。

async 函数的实现

async 函数的实现,就是将Generator函数和自动执行器,包装在一个函数里。

async function fn(args) {
    // ...
}

// 等同于

function fn(args) {
    return spawn(function*() {
        
    })
}

所有的 async 函数都可以写成上面的第二种形式,其中的 spawn 函数就是自动执行器。

下面给出 spawn 函数的实现,基本就是前文自动执行器的翻版。

/**
 * 模拟 async 函数的实现,该段代码取自 Secrets of the JavaScript Ninja (Second Edition),p159
 */
// 接收生成器作为参数,建议先移到后面,看下生成器中的代码
    var myAsync = generator => {
        // 注意 iterator.next() 返回对象的 value 是 promiseAjax(),一个 promise
        const iterator = generator();
//        console.log('--->', iterator)
        // handle 函数控制 async 函数的 挂起-执行
        const handle = iteratorResult => {
//            console.log('---11>', iteratorResult)  // 如果是true的话它就对代码进行阻断了
            if (iteratorResult.done) return;
//            console.log('--->', iteratorResult)  // 如果是promise是会挂起的
            const iteratorValue = iteratorResult.value;

            // 只考虑异步请求返回值是 promise 的情况
            if (iteratorValue instanceof Promise) {
                // 递归调用 handle,promise 兑现后再调用 iterator.next() 使生成器继续执行
                // ps.原书then最后少了半个括号 ')'
                iteratorValue
                        .then(result => handle(iterator.next(result)))
                        .catch(e => iterator.throw(e));
            }
        };

        try {
            handle(iterator.next());
        } catch (e) {
            console.log(e);
        }
    };
    myAsync(function*() {
        try {
            const a = yield Promise.resolve(1);
            const b = yield Promise.resolve(a + 10);
            const c = yield Promise.resolve(b + 100);
            console.log(a, b, c); // 输出 1,11,111
        } catch (e) {
            console.log("出错了:", e);
        }
    })

我没有看阮老师的代码,从这段代码来看,async 函数 只是构造器函数的封装也就是 generator 函数的封装。 iteratorResult.done一直是false,直到 iteratorResult.done 为 true,函数这个时候才阻断执行。

我就粘贴下掘金原作者的话,然后大家看一眼。

1)首先调用generator()生成它的控制器,即迭代器iterator,此时,生成器处于挂起状态;
2)第一次调用handle函数,并传入iterator.next(),这样就完成生成器的第一次调用的;
3)执行生成器,遇到yield生成器再次挂起,同时把yield后表达式的结果(未完成的 promise)传给 handle;
4)生成器挂起的同时,异步请求还在进行,异步请求完成(promise 兑现)后,会调用handle函数中的iteratorValue.then();
5)iteratorValue.then()执行时内部递归调用handle,同时把异步请求回的数据传给生成器(iterator.next(result)),生成器更新数据再次执行。如果出错直接结束;
6)3、4、5 步重复执行,直到生成器结束,即iteratorResult.done === true,myAsync 结束调用。


https://juejin.im/post/5b56837c6fb9a04fe0181555
普通函数使用 function 声明,生成器函数用 function*声明
普通函数使用 return 返回值,生成器函数使用 yield 返回值
普通函数是 run to completion 模式,即普通函数开始执行后,会一直执行到该函数所有语句完成,在此期间别的代码语句是不会被执行的;生成器函数是 run-pause-run 模式,即生成器函数可以在函数运行中被暂停一次或多次,并且在后面再恢复执行,在暂停期间允许其他代码语句被执行
对于 Generators 的使用,本文不再多做介绍,如需了解更多内容推荐阅读下面系列文章,《ES6 Generators: Complete Series》或者 《深入掌握 ECMAScript 6 异步编程》系列文章

http://www.alloyteam.com/2016/02/generators-in-depth/

以上是对 async 函数的简单理解