ES6 之 Generator 异步应用
2018-03-09 10:21
411 查看
1、简介
异步编程对 JavaScript 语言太重要。Javascript 语言的执行环境是 “单线程” 的,如果没有异步编程,根本没法用,非卡死不可。现在主要介绍 Generator 函数如何完成异步操作。// 异步编程:回调函数 fs.readFile('/etc/passwd', 'utf-8', function (err, data) { if (err) throw err; console.log(data); }); // 异步编程:Promise var readFile = require('fs-readfile-promise'); readFile(fileA) .then(function (data) { console.log(data.toString()); }) .then(function () { return readFile(fileB); }) .then(function (data) { console.log(data.toString()); }) .catch(function (err) { console.log(err); }); // 异步编程:Generator function* gen(x) { var y = yield x + 2; return y; } var g = gen(1); g.next() // { value: 3, done: false } g.next() // { value: undefined, done: true } // 异步任务的封装:异步操作表示得很简洁,但是流程管理却不方便(即何时执行第一阶段、何时执行第二阶段) 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){ // 由于 Fetch 模块返回的是一个 Promise 对象,因此要用 then 方法调用下一个 next 方法 return data.json(); }).then(function(data){ g.next(data); });
2、JavaScript 语言的 Thunk 函数
在 JavaScript 语言中,Thunk 函数替换的是多参数函数,将其替换成一个只接受回调函数作为参数的单参数函数。// 正常版本的readFile(多参数版本) fs.readFile(fileName, callback); // Thunk版本的readFile(单参数版本) var Thunk = function (fileName) { return function (callback) { return fs.readFile(fileName, callback); }; }; var readFileThunk = Thunk(fileName); readFileThunk(callback); // Generator 函数自动执行,不过不适合异步操作。如果必须保证前一步执行完,才能执行后一步,则下面的自动执行就不可行。 function* gen() { // ... } var g = gen(); var res = g.next(); while(!res.done){ console.log(res.value); res = g.next(); } // Thunk 函数的自动流程管理 var fs = require('fs'); function run(generator) { var it = generator(go); function go(err, result) { if (err) return it.throw(err); it.next(result); } go(); } run(function* (done) { var firstFile; try { var dirFiles = yield fs.readdir('NoNoNoNo', done); // No such dir firstFile = dirFiles[0]; } catch (err) { firstFile = null; } console.log(firstFile); });
3、co 模块
co 模块是著名程序员 TJ Holowaychuk 于 2013 年 6 月发布的一个小工具,用于 Generator 函数的自动执行。co 模块其实就是将两种自动执行器(Thunk 函数和 Promise 对象),包装成一个模块。使用 co 的前提条件是,Generator 函数的 yield 命令后面,只能是 Thunk 函数或 Promise 对
象。如果数组或对象的成员,全部都是 Promise 对象,也可以使用 co。这样当异步操作有了结果就能够用 then 方法交回执行权。
// co 模块可以让你不用编写 Generator 函数的执行器,Generator 函数只要传入 co 函数,就会自动执行,并返回一个 Promise 对象 var readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; var gen = function* () { var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }; var co = require('co'); co(gen); // 基于 Promise 对象的自动执行 function getFoo() { return new Promise(function (resolve, reject) { resolve('foo'); }); } function run(generator) { var it = generator(); function go(result) { if (result.done) return result.value; return result.value.then(function (value) { return go(it.next(value)); }, function (error) { return go(it.throw(error)); }); } go(it.next()); } run(function* () { try { var foo = yield getFoo(); console.log(foo); } catch (e) { console.log(e); } }); // co 源码---------------------------------------------》 var slice = Array.prototype.slice; module.exports = co['default'] = co.co = co; // 将生成器函数 fn 封装到函数中,这个函数执行返回 Promise co.wrap = function (fn) { createPromise.__generatorFunction__ = fn; return createPromise; function createPromise() { return co.call(this, fn.apply(this, arguments)); } }; // 执行生成器函数或者迭代器,返回 Promise function co(gen) { var ctx = this; var args = slice.call(arguments, 1); return new Promise(function (resolve, reject) { if (typeof gen === 'function') gen = gen.apply(ctx, args); // 调用生成器函数来获得遍历器 if (!gen || typeof gen.next !== 'function') return resolve(gen); // 如果不是遍历器,那么直接将 Promise 状态设置为 resolved // 手动执行一次 onFulfilled(); // 成功回调函数 function onFulfilled(res) { var ret; try { ret = gen.next(res); // 首次启动遍历器遍历,将返回值给 ret 变量 } catch (e) { return reject(e); // 如果执行失败将 Promise 状态设置为 rejected } next(ret); return null; } // 失败回调函数 function onRejected(err) { var ret; try { ret = gen.throw(err); // 抛出错误 } catch (e) { return reject(e); // 将 Promise 状态设置为 rejected } next(ret); } // 主要是将 onFulfilled、onRejected 函数添加到 ret.value 或者其转化的 Promise 中的回调函数中 function next(ret) { if (ret.done) return resolve(ret.value); // 如果已经遍历完成,那么返回 ret.value var value = toPromise.call(ctx, ret.value); // 将 ret.value 转化为 Promise if (value && isPromise(value)) return value.then(onFulfilled, onRejected); // 将 onFulfilled 和 onRejected 添加到 Promise 的回调函数中 return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + 'but the following object was passed: "' + String(ret.value) + '"')); // 如果返回的数据转化失败,那么抛出错误,将 Promise 状态设置为 rejected } }); } // 将参数 obj 转为 Promise function toPromise(obj) { if (!obj) return obj; if (isPromise(obj)) return obj; if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); // 如果是生成器函数,那么执行 co 返回 Promise if ('function' == typeof obj) return thunkToPromise.call(this, obj); if (Array.isArray(obj)) return arrayToPromise.call(this, obj); if (isObject(obj)) return objectToPromise.call(this, obj); return obj; } // 将 thunk 函数转换为 Promise function thunkToPromise(fn) { var ctx = this; return new Promise(function (resolve, reject) { fn.call(ctx, function (err, res) { if (err) return reject(err); if (arguments.length > 2) res = slice.call(arguments, 1); resolve(res); }); }); } // 将数组转换为 Promise function arrayToPromise(obj) { return Promise.all(obj.map(toPromise, this)); } // 将 obj 转换为 Promise function objectToPromise(obj) { var results = new obj.constructor(); // 获取一个新的 obj 对象 results var keys = Object.keys(obj); // 获取对象的 keys var promises = []; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var promise = toPromise.call(this, obj[key]); // 将对象的 values 转为 promise 对象 if (promise && isPromise(promise)) defer(promise, key); // 如果转换成功执行 defer else results[key] = obj[key]; // 如果转换失败则传值给新对象 } return Promise.all(promises).then(function () { // 将所有的 promise 合并为一个新的 promise 并添加回调函数 return results; // 返回新对象 results }); function defer(promise, key) { // predefine the key in the result results[key] = undefined; // 将新对象 results 的对应 value 设置为 undefined promises.push(promise.then(function (res) { // 为新 promise 添加回调函数,并将值传入 promises 数组 results[key] = res; // 回调函数中将新对象 results 的对应 value 设置为原值 })); } } // 判断参数 obj 是否是 promise 对象 function isPromise(obj) { return 'function' == typeof obj.then; } // 判断参数 obj 是否是迭代器 function isGenerator(obj) { return 'function' == typeof obj.next && 'function' == typeof obj.throw; } // 判断参数 obj 是否是生成器函数或者 generator 生成的迭代器 function isGeneratorFunction(obj) { var constructor = obj.constructor; // ƒ GeneratorFunction() { [native code] } if (!constructor) return false; // displayName 属性将返回函数的显示名称;name(ES6) 属性返回一个函数声明的名称 if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; return isGenerator(constructor.prototype); // 判断是否是 generator 生成的迭代器,constructor.prototype 返回 Generator 对象 } // 判断是否为简单对象 function isObject(val) { return Object == val.constructor; }
4、Generator 应用
Generator 可以暂停函数执行,返回任意表达式的值。这种特点使得 Generator 有多种应用场景。// 异步操作的同步化表达 function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next(); // 控制流管理(这里只适合同步操作,异步操作使用 Thunk 或者 co) scheduler(longRunningTask(initialValue)); function scheduler(task) { var taskObj = task.next(task.value); // 如果Generator函数未结束,就继续调用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } } // 部署 Iterator 接口 function* iterEntries(obj) { let keys = Object.keys(obj); for (let i=0; i < keys.length; i++) { let key = keys[i]; yield [key, obj[key]]; } } let myObj = { foo: 3, bar: 7 }; for (let [key, value] of iterEntries(myObj)) { console.log(key, value); } // 作为数据结构(Generator 使得数据或者操作,具备了类似数组的接口) function* doStuff() { yield fs.readFile.bind(null, 'hello.txt'); yield fs.readFile.bind(null, 'world.txt'); yield fs.readFile.bind(null, 'and-such.txt'); } for (task of doStuff()) { // task是一个函数,可以像回调函数那样使用它 } function doStuff() { // 可以用数组模拟 Generator 的这种用法 return [ fs.readFile.bind(null, 'hello.txt'), fs.readFile.bind(null, 'world.txt'), fs.readFile.bind(null, 'and-such.txt') ]; }
相关文章推荐
- ES6 —(Generator 函数的异步应用)
- Generator函数异步应用
- Generator 函数的异步应用
- 17.0 generator函数的异步应用
- iFrame应用实例讲解——页面无需刷新的异步文件上传!
- Ajax异步上传在SSM框架中的应用
- python中的generator(coroutine)浅析和应用
- PHP 异步执行方法,模拟多线程的应用分析
- 【转】Android应用中使用AsyncHttpClient来异步网络数据
- Android应用中使用AsyncHttpClient来异步网络数据
- Gray码在异步FIFO中的应用
- express-generator生成express应用——笔记
- Vert.x,一个异步、可伸缩、并发应用框架引发的思考
- 基于C#的MongoDB数据库开发应用(3)--MongoDB数据库的C#开发之异步接口
- 五种IO 模式——阻塞(默认IO模式),非阻塞(常用语管道),IO多路复用(IO多路复用的应用场景),信号IO,异步IO
- 五种I/O 模式——阻塞(默认IO模式),非阻塞(常用语管道),I/O多路复用(IO多路复用的应用场景),信号I/O,异步I/O
- 易生活(一)-APP---回调函数在异步操作中的应用
- Vert.x,一个异步、可伸缩、并发应用框架引发的思考
- Android:异步处理之AsyncTask的应用(二)
- 消息机制及应用-同步异步