一、异步任务运行
执行异步操作的传统方式是调用一个包含回调的函数,例如:
let fs = require("fs");
fs.readFile("config.json", function(err, contents) {
if (err) {
throw err;
}
doSomethingWith(contents);
console.log("Done");
});
当你拥有数量少而有限的任务需要完成时,这么做很有效;然而当你需要嵌套回调函数,或者要按顺序处理一系列的异步任务时,此方式就会非常麻烦了。在这种场合下,生成器与 yield 会很有用。
二、生成器实现异步编程
由于 yield 能停止运行,并在重新开始运行前等待 next() 方法被调用,你就可以在没有回调函数的情况下实现异步调用。
let fs = require("fs");
function run(taskDef) {
// 创建迭代器,让它在别处可用
let task = taskDef();
// 开始任务
let result = task.next();
// 递归使用函数来保持对 next() 的调用
function step() {
// 如果还有更多要做的
if (!result.done) {
if (typeof result.value === "function") {
result.value(function(err, data) {
if (err) {
result = task.throw(err);
return;
}
result = task.next(data);
step();
});
} else {
result = task.next(result.value);
step();
}
}
}
// 开始处理过程
step();
}
// 定义一个函数来配合任务运行器使用
function readFile(filename) {
return function(callback) {
fs.readFile(filename, callback);
};
}
// 运行一个任务
run(function*() {
let contents = yield readFile("config.json");
doSomethingWith(contents);
console.log("Done");
});
此例执行了异步的 readFile() 操作,而在主要代码中并未暴露出任何回调函数。除了yield 之外,此代码看起来与同步代码并无二致。既然执行异步操作的函数都遵循了同一接口,你就可以用貌似同步的代码来书写处理逻辑。当然,这些范例中所使用的模式也有缺点:你无法完全确认一个能返回函数的函数是异步的。
三、Promise与Generator结合实现异步编程
借助 Promise ,你可以确保每个异步操作都返回一个 Promise ,从而大幅度简化并一般化异步处理,通用接口也意味着你可以大大减少异步代码。
let fs = require("fs");
function run(taskDef) {
// 创建迭代器
let task = taskDef();
// 启动任务
let result = task.next();
// 递归使用函数来进行迭代
(function step() {
// 如果还有更多要做的
if (!result.done) {
// 决议一个 Promise ,让任务处理变简单
let promise = Promise.resolve(result.value);
promise.then(function(value) {
result = task.next(value);
step();
}).catch(function(error) {
result = task.throw(error);
step();
});
}
}());
}
// 定义一个函数来配合任务运行器使用
function readFile(filename) {
return new Promise(function(resolve, reject) {
fs.readFile(filename, function(err, contents) {
if (err) {
reject(err);
} else {
resolve(contents);
}
});
});
}
// 运行一个任务
run(function*() {
let contents = yield readFile("config.json");//readFile执行异步操作
doSomethingWith(contents);
console.log("Done");
});
调用 Promise.resolve() 只为预防未正确返回Promise 的函数(记住: Promise.resolve() 在被传入任意 Promise 时只会直接将其传递回来,而不是 Promise 的参数则会被包装为 Promise )。
run() 函数能运行任意使用 yield 来实现异步代码的生成器,而不会将 Promise (或回调函数)暴露给开发者。事实上,由于函数调用后的返回值总是会被转换为一个 Promise ,该函数甚至允许返回 Promise 之外的类型。这意味着同步与异步方法在使用 yield 时都会正常工作,并且你永不需要检查返回值是否为一个 Promise 。唯一需要担心的是,要确保诸如 readFile() 的异步方法能返回一个正确标记其状态的Promise 。
若只是单纯的使用Promise实现异步编程,只是回调函数的改进,使用then
方法以后,异步任务的两段执行看得更清楚了,除此以外,并无新意。并且原来的任务被 Promise 包装了一下,不管什么操作,一眼看去都是一堆then
,原来的语义变得很不清楚。所以将Promise和Generator结合使用,效果最佳。