ES6 诞生以前,异步编程的方法,大概有下面四种。
- 回调函数
- 事件监听
- 发布/订阅
- Promise 对象
协程的Generator函数实现
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield
语句注明。
Generator函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案:函数体内外的数据交换和错误处理机制。
next
返回值的value
属性,是Generator函数向外输出数据;next
方法还可以接受参数,向Generator函数体内输入数据。
Generator函数体外,使用指针对象的throw
方法抛出的错误,可以被函数体内的try...catch
代码块捕获。这意味着,出错的代码与处理错误的代码,实现了时间和空间上的分离,这对于异步编程无疑是很重要的。
异步任务的封装
const URL = 'https://api.github.com/users/github';
function * gg() {
let res = yield fetch(URL);
console.log(res)
}
let re = gg();
re.next().value.then(val = > val.json())
.then(val = > console.log(val))
上面代码中,首先执行Generator函数,获取遍历器对象,然后使用next
方法,执行异步任务的第一阶段。由于Fetch
返回的是一个Promise
对象,因此要用then
方法调用下一个next
方法。
可以看到,虽然Generator函数将异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段)。
Thunk函数
编译器的“传名调用”实现,往往是将参数放到一个临时函数之中,再将这个临时函数传入函数体。这个临时函数就叫做Thunk函数。
function f(m) {
return m * 2;
}
f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};
function f(thunk) {
return thunk() * 2;
}
上面代码中,函数f
的参数x+5
被一个函数替换了。凡是用到原参数的地方,对Thunk函数求值即可。
这就是 Thunk 函数的定义,它是“传名调用”的一种实现策略,用来替换某个表达式。
JavaScript语言的Thunk函数
JavaScript语言是传值调用,它的Thunk函数含义有所不同。Thunk函数替换的不是表达式,而是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。
co模块
co模块是著名程序员TJ Holowaychuk于2013年6月发布的一个小工具,用于Generator函数的自动执行。
下面是一个Generator函数,用于依次读取两个文件。
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
co模块可以让你不用编写Generator
函数的执行器。
var co = require('co');
co(gen);
上面代码中,Generator函数只要传入co
函数,就会自动执行。
co
函数返回一个Promise
对象,因此可以用then
方法添加回调函数。
co(gen).then(function (){
console.log('Generator 函数执行完成');
});