Modern JavaScript - Async Functions

  1. 1. Callback Hell
  2. 2. Bluebrid
  3. 3. Syntax
  4. 4. DEMO
  5. 5. co
  • REF::
  • http://node.green/#async-functions

    Node 的最大特性之一就是 Single Thread, Event-Drive, Asynchronous I/O。

    因为是单线程,注定是不能长时间空等占用线程,JS 的解决方案是 Asynchronous Event,当有事件通知到时做响应,需要等待时,将控制权交出。

    对于 Asynchronous,那真是有太多的方案了,我们从头开始看:


    ## Callback

    最早通过 Callback 来实现 Event-Drive。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function f1(callback){
    setTimeout(function () {
    // f1的任务代码
    callback();
    }, 1000);
    }

    // 调用
    f1(f2);

    callback 的原理就是我向事件中心注册这么一个事件,当执行到我时,事件中心来调用我这个事件。

    Callback Hell

    当然 Callback 有着最大的天然缺陷,不符合人类思维,当项目变大,嵌套变深时,callback 的写法,将是一个灾难:


    ## Promise

    Promise 对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。

    Promise 是一个有限状态机(有 Pending, Resolved, Rejected 三种状态),一旦创建后立即执行,可以由 Pending 到另外两种,状态一旦改变后就不能再次更改,会一直保持。

    和 Callback 不同的是,状态会一直保持,在 Promise 执行完成后,依然可以添加回调 (then)。

    原理就是有一个队列,回调时从里面取出,放到 Event Queue 中。

    将传统的 Callback 封装成 Promise 来使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const asyncStringify = (content) => {
    return new Promise(function promiser(resolve, reject) {
    stringify(content, function (err, output) {
    if (err) {
    return reject(err);
    }

    return resolve(output);
    });
    });
    };

    Promise 创建后立即执行,然后可以用 then 方法分别指定 Resolved 状态和 Reject 状态的回调函数。

    1
    2
    3
    4
    5
    6
    7
    8
    promise.then(function(value) {
    // success
    }, function(error) {
    // failure
    });

    # Arrow function looks beatul
    promise.then((value) => { ... });

    Bluebrid

    Bluebrid 提供了方法,可以直接将 所有的 Callback 直接返回全新的 promise。

    这里我们的 系统的 FileStream 模块的所有函数给转成 Promise 函数。

    1
    2
    3
    4
    import bluebird from 'bluebird';
    import fs from 'fs';

    let asyncFs = bluebird.promisifyAll(fs);

    Promise 的写法只是回调函数的改进,使用then方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。

    Promise 的最大问题是代码冗余,原来的任务被Promise 包装了一下,不管什么操作,一眼看去都是一堆 then,原来的语义变得很不清楚。


    ## Generator

    Generator 是一个无限状态机。

    Syntax

    1
    2
    3
    function *foo() {
    // ..
    }

    DEMO

    1
    2
    3
    4
    5
    6
    7
    8
    function *helloWorldGenerator() {
    console.log("hi");
    yield 'hello';
    yield 'world';
    return 'ending';
    }

    var hw = helloWorldGenerator();

    和普通 function 有点不同的是,这个函数并不会执行,返回的也不是结果,而是指向内部状态的指针对象(遍历器),普通函数中,是不能指用 yield 的。

    然后通过遍历器对象的 next 方法,使指针从函数头部向下移一个状态。直到遇到下一个 yield 语句。

    Generator 函数是分段执行的,yield 语句是暂停执行的标记,而 next 方法可以恢复执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    hw.next();
    // hi
    // Object {value: "hello", done: false}

    hw.next();
    // Object {value: "world", done: false}

    hw.next();
    // Object {value: "ending", done: true}

    hw.next();
    // Object {value: undefined, done: true}

    next 方法的参数

    yield 句本身没有返回值,或者说总是返回 undefined。next 方法可以带一个参数,该参数就会被当作上一个 yield 语句的返回值。

    co

    Generator 经常被用做顺序执行异步流程。

    但 next 函数要一次次的写,很烦,于是 有了各种方案。

    • Thunk
    • co

    co 函数接收一个 generator 为参数,然后就会自动执行 generator。

    同时,co 函数返回一个 Promise 对象,因此可以用 then 方法添加回调函数。


    ## Async Functions

    Async Fuctions 其实只是 Generator 的语法糖,外带一个类 co 的自动执行器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var gen = function* (){
    var f1 = yield readFile('/etc/fstab');
    console.log(f1.toString());
    };

    // 写成async函数,就是下面这样。
    var asyncReadFile = async function (){
    var f1 = await readFile('/etc/fstab');
    console.log(f1.toString());
    };
    • 语法上,将 * 换成了 async,yield 换成了 await。
    • 语义上更清晰,一看就知道这是一个异步函数。需要 await 等待结果。
    • 内置执行器,不用依赖 co。
    • 返回值是Promise。

    ## 常用库的 Promise 版

    REF::