ES6 系列之 Generator 的自动执行的方法示例

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 函数,获取遍历器对象。

然后使用 next 方法,执行异步任务的第一阶段,即 fetch(url)。

注意,由于 fetch(url) 会返回一个 Promise 对象,所以 result 的值为:

{ value: Promise { <pending> }, done: false }

最后我们为这个 Promise 对象添加一个 then 方法,先将其返回的数据格式化(data.json()),再调用 g.next,将获得的数据传进去,由此可以执行异步任务的第二阶段,代码执行完毕。

多个异步任务

上节我们只调用了一个接口,那如果我们调用了多个接口,使用了多个 yield,我们岂不是要在 then 函数中不断的嵌套下去……

所以我们来看看执行多个异步任务的情况:

var fetch = require('node-fetch'); function* gen() { var r1 = yield fetch('https://api.github.com/users/github'); var r2 = yield fetch('https://api.github.com/users/github/followers'); var r3 = yield fetch('https://api.github.com/users/github/repos'); console.log([r1.bio, r2[0].login, r3[0].full_name].join('\n')); }

为了获得最终的执行结果,你可能要写成:

var g = gen(); var result1 = g.next(); result1.value.then(function(data){ return data.json(); }) .then(function(data){ return g.next(data).value; }) .then(function(data){ return data.json(); }) .then(function(data){ return g.next(data).value }) .then(function(data){ return data.json(); }) .then(function(data){ g.next(data) });

但我知道你肯定不想写成这样……

其实,利用递归,我们可以这样写:

function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value.then(function(data) { return data.json(); }).then(function(data) { next(data); }); } next(); } run(gen);

其中的关键就是 yield 的时候返回一个 Promise 对象,给这个 Promise 对象添加 then 方法,当异步操作成功时执行 then 中的 onFullfilled 函数,onFullfilled 函数中又去执行 g.next,从而让 Generator 继续执行,然后再返回一个 Promise,再在成功时执行 g.next,然后再返回……

启动器函数

在 run 这个启动器函数中,我们在 then 函数中将数据格式化 data.json(),但在更广泛的情况下,比如 yield 直接跟一个 Promise,而非一个 fetch 函数返回的 Promise,因为没有 json 方法,代码就会报错。所以为了更具备通用性,连同这个例子和启动器,我们修改为:

var fetch = require('node-fetch'); function* gen() { var r1 = yield fetch('https://api.github.com/users/github'); var json1 = yield r1.json(); var r2 = yield fetch('https://api.github.com/users/github/followers'); var json2 = yield r2.json(); var r3 = yield fetch('https://api.github.com/users/github/repos'); var json3 = yield r3.json(); console.log([json1.bio, json2[0].login, json3[0].full_name].join('\n')); } function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value.then(function(data) { next(data); }); } next(); } run(gen);

只要 yield 后跟着一个 Promise 对象,我们就可以利用这个 run 函数将 Generator 函数自动执行。

回调函数

yield 后一定要跟着一个 Promise 对象才能保证 Generator 的自动执行吗?如果只是一个回调函数呢?我们来看个例子:

首先我们来模拟一个普通的异步请求:

function fetchData(url, cb) { setTimeout(function(){ cb({status: 200, data: url}) }, 1000) }

我们将这种函数改造成:

function fetchData(url) { return function(cb){ setTimeout(function(){ cb({status: 200, data: url}) }, 1000) } }

对于这样的 Generator 函数:

function* gen() { var r1 = yield fetchData('https://api.github.com/users/github'); var r2 = yield fetchData('https://api.github.com/users/github/followers'); console.log([r1.data, r2.data].join('\n')); }

如果要获得最终的结果:

var g = gen(); var r1 = g.next(); r1.value(function(data) { var r2 = g.next(data); r2.value(function(data) { g.next(data); }); });

如果写成这样的话,我们会面临跟第一节同样的问题,那就是当使用多个 yield 时,代码会循环嵌套起来……

同样利用递归,所以我们可以将其改造为:

function run(gen) { var g = gen(); function next(data) { var result = g.next(data); if (result.done) return; result.value(next); } next(); } run(gen);

run

由此可以看到 Generator 函数的自动执行需要一种机制,即当异步操作有了结果,能够自动交回执行权。

而两种方法可以做到这一点。

(1)回调函数。将异步操作进行包装,暴露出回调函数,在回调函数里面交回执行权。

(2)Promise 对象。将异步操作包装成 Promise 对象,用 then 方法交回执行权。

在两种方法中,我们各写了一个 run 启动器函数,那我们能不能将这两种方式结合在一些,写一个通用的 run 函数呢?我们尝试一下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/9bf736ee6f93c48378cc2b5c673c5222.html