Modern JavaScript - Async Functions

  1. 1. Callback
    1. 1.1. Callback Hell
  2. 2. Promise
    1. 2.1. Bluebrid
  3. 3. Generator
    1. 3.1. Syntax
    2. 3.2. DEMO
    3. 3.3. co
  4. 4. Async Functions
  5. 5. 常用库的 Promise 版
  6. 6. 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
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
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::