ES6 Learning - 异步操作 & Async函数

ES6之前,异步编程的方法大概四种。

回调函数

事件监听

发布 / 订阅

Promise对象

基本概念

异步

一个任务分成几段之后,可以先执行第一段,然后转而执行其他任务,等准备好之后再执行第二段。

同步

一个任务分成几段之后,段与段之间不能插入其他任务,必须连续执行,这段时间其他任务只能干等着。

回调函数

任务分成几段之后,第一段中的function定义了第二段函数的参数,这时第二个参数就是回调函数。

Promise

new Promise().then().then()...

Generator函数

协程

多个线程互相协作,完成异步任务。

1
2
3
4
5
function *asnycJob() {
// ...其他代码
var f = yield readFile(fileA);
// ...其他代码
}

奥妙就在于yield命令的使用。

异步任务的封装

看一个案例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var fetch = require('node-fetch');

function* gen(){
var url = 'https://api.github.com/users/github';
var result = yield fetch(url);
console.log(result.bio);
}

var g = gen();
var result = g.next();

result.value.then(function(data){
return data.json();
}).then(function(data){
g.next(data);
});

例子里用Generator函数实现了异步执行,但是连续调用g.next()显得流程管理上不是那么方便。

Thunk函数

参数的求值策略

传值调用还是传名调用。传值调用明显更通俗易懂,而且简单实用。而传名调用可能会有性能上的优化。

1
2
3
4
5
function f(a, b){
return b;
}

f(3 * x * x - 2 * x - 1, x);

Thunk函数的含义

传名调用实现就是将参数放到一个临时函数中,再将这个临时函数传入函数体。这个临时函数就是Thunk函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function f(m){
return m * 2;
}

f(x + 5);

// 等同于

var thunk = function () {
return x + 5;
};

function f(thunk){
return thunk() * 2;
}

Thunk in Javascript

Javascript是执行的传值调用。Thunk将多参数函数替换成单参数版本,只接受回调函数作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);

var Thunk = function (fileName){
return function (callback){
return fs.readFile(fileName, callback);
};
};

一个简单的Thunk函数转换器。

1
2
3
4
5
6
7
8
9
10
11
12
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};

var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

Generator函数流程管理

正常情况来说,Generator函数封装的异步操作可以如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

var gen = function* (){
var r1 = yield readFile('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFile('/etc/shells');
console.log(r2.toString());
};

var g = gen();

var r1 = g.next();
r1.value(function(err, data){
if (err) throw err;
var r2 = g.next(data);
r2.value(function(err, data){
if (err) throw err;
g.next(data);
});
});