本篇文章给大家带来的内容是关于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 函数呢?我们尝试一下:
// 第一版 function run(gen) { var gen = gen(); function next(data) { var result = gen.next(data); if (result.done) return; if (isPromise(result.value)) { result.value.then(function(data) { next(data); }); } else { result.value(next) } } next() } function isPromise(obj) { return 'function' == typeof obj.then; } module.exports = run;
其实实现的很简单,判断 result.value 是否是 Promise,是就添加 then 函数,不是就直接执行。
return Promise
我们已经写了一个不错的启动器函数,支持 yield 后跟回调函数或者 Promise 对象。
现在有一个问题需要思考,就是我们如何获得 Generator 函数的返回值呢?又如果 Generator 函数中出现了错误,就比如 fetch 了一个不存在的接口,这个错误该如何捕获呢?
这很容易让人想到 Promise,如果这个启动器函数返回一个 Promise,我们就可以给这个 Promise 对象添加 then 函数,当所有的异步操作执行成功后,我们执行 onFullfilled 函数,如果有任何失败,就执行 onRejected 函数。
我们写一版:
// 第二版 function run(gen) { var gen = gen(); return new Promise(function(resolve, reject) { function next(data) { try { var result = gen.next(data); } catch (e) { return reject(e); } if (result.done) { return resolve(result.value) }; var value = toPromise(result.value); value.then(function(data) { next(data); }, function(e) { reject(e) }); } next() }) } function isPromise(obj) { return 'function' == typeof obj.then; } function toPromise(obj) { if (isPromise(obj)) return obj; if ('function' == typeof obj) return thunkToPromise(obj); return obj; } function thunkToPromise(fn) { return new Promise(function(resolve, reject) { fn(function(err, res) { if (err) return reject(err); resolve(res); }); }); } module.exports = run;
与第一版有很大的不同:
首先,我们返回了一个 Promise,当 result.done 为 true 的时候,我们将该值 resolve(result.value),如果执行的过程中出现错误,被 catch 住,我们会将原因 reject(e)。
其次,我们会使用 thunkToPromise 将回调函数包装成一个 Promise,然后统一的添加 then 函数。在这里值得注意的是,在 thunkToPromise 函数中,我们遵循了 error first 的原则,这意味着当我们处理回调函数的情况时:
// 模拟数据请求 function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) } }
在成功时,第一个参数应该返回 null,表示没有错误原因。
优化
我们在第二版的基础上将代码写的更加简洁优雅一点,最终的代码如下:
// 第三版 function run(gen) { return new Promise(function(resolve, reject) { if (typeof gen == 'function') gen = gen(); // 如果 gen 不是一个迭代器 if (!gen || typeof gen.next !== 'function') return resolve(gen) onFulfilled(); function onFulfilled(res) { var ret; try { ret = gen.next(res); } catch (e) { return reject(e); } next(ret); } function onRejected(err) { var ret; try { ret = gen.throw(err); } catch (e) { return reject(e); } next(ret); } function next(ret) { if (ret.done) return resolve(ret.value); var value = toPromise(ret.value); if (value && isPromise(value)) return value.then(onFulfilled, onRejected); return onRejected(new TypeError('You may only yield a function, promise ' + 'but the following object was passed: "' + String(ret.value) + '"')); } }) } function isPromise(obj) { return 'function' == typeof obj.then; } function toPromise(obj) { if (isPromise(obj)) return obj; if ('function' == typeof obj) return thunkToPromise(obj); return obj; } function thunkToPromise(fn) { return new Promise(function(resolve, reject) { fn(function(err, res) { if (err) return reject(err); resolve(res); }); }); } module.exports = run;
co
如果我们再将这个启动器函数写的完善一些,我们就相当于写了一个 co,实际上,上面的代码确实是来自于 co……
而 co 是什么? co 是大神 TJ Holowaychuk 于 2013 年 6 月发布的一个小模块,用于 Generator 函数的自动执行。
如果直接使用 co 模块,这两种不同的例子可以简写为:
// yield 后是一个 Promise var fetch = require('node-fetch'); var co = require('co'); 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')); } co(gen);
// yield 后是一个回调函数 var co = require('co'); function fetchData(url) { return function(cb) { setTimeout(function() { cb(null, { status: 200, data: url }) }, 1000) } } 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')); } co(gen);
是不是特别的好用?
以上是ES6中Generator的自动执行详解的详细内容。更多信息请关注PHP中文网其他相关文章!

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

理解JavaScript引擎内部工作原理对开发者重要,因为它能帮助编写更高效的代码并理解性能瓶颈和优化策略。1)引擎的工作流程包括解析、编译和执行三个阶段;2)执行过程中,引擎会进行动态优化,如内联缓存和隐藏类;3)最佳实践包括避免全局变量、优化循环、使用const和let,以及避免过度使用闭包。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

Python和JavaScript在社区、库和资源方面的对比各有优劣。1)Python社区友好,适合初学者,但前端开发资源不如JavaScript丰富。2)Python在数据科学和机器学习库方面强大,JavaScript则在前端开发库和框架上更胜一筹。3)两者的学习资源都丰富,但Python适合从官方文档开始,JavaScript则以MDNWebDocs为佳。选择应基于项目需求和个人兴趣。

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

JavaScript在现实世界中的应用包括服务器端编程、移动应用开发和物联网控制:1.通过Node.js实现服务器端编程,适用于高并发请求处理。2.通过ReactNative进行移动应用开发,支持跨平台部署。3.通过Johnny-Five库用于物联网设备控制,适用于硬件交互。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

Dreamweaver Mac版
视觉化网页开发工具

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

安全考试浏览器
Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。