资讯专栏INFORMATION COLUMN

异步编程解决方案 - generator

AbnerMing / 840人阅读

摘要:异步编程解决方案事件发布订阅模式订阅,发布事件监听是一种高阶函数的应用,通过事件可以把内部数据传递给外部的调用者,编程者可以不用关心组件内部如何执行,只需关注在需要的事件点上即可。

异步编程难点 异常处理

在处理异常时经常用try/catch/final语句块进行异常捕获,但是这种异常捕获对异步编程并不是用

</>复制代码

  1. function async(callback) {
  2. process.nextTick(callback);
  3. }
  4. try {
  5. async(function () {
  6. console.log(a);
  7. });
  8. } catch (err) {
  9. // TODO
  10. }

异步代码分为两个过程,提交请求和处理结果,其中代码在异步处理完成之前返回,而异常不一定在这个过程中发生,所以try、catch不会有任何作用,调用async时,callback被暂时挂起,等到代码执行完毕才会执行,try只能捕获当前事件循环的异常,对下一次的事件循环无法处理(nodejs异步时间做了约定,异常一定被当成第一个参数传回,在调用callback时先判断是否有异常发生)

</>复制代码

  1. function async(callback) {
  2. process.nextTick(function () {
  3. if (err) {
  4. return callback(err);
  5. }
  6. callback(null);
  7. });
  8. }
  9. try {
  10. async(function (err) {
  11. if (!err) {
  12. console.log(a);
  13. }
  14. });
  15. } catch (err) {
  16. // TODO
  17. }
函数嵌套过深

对于Node和agax调用而言,有时会存在多个异步调用嵌套的场景,比如一个文件目录的遍历操作:

</>复制代码

  1. fs.readdir(path.join(__dirname, ".."), function (err, file) {
  2. files.forEach(function (filename, index) {
  3. fs.readFile(filename, "utf8), function (err, file) {
  4. // TODO
  5. }
  6. });
  7. });

或者一个网页渲染操作:

</>复制代码

  1. $(selector).click(function (e) {
  2. $ajax({
  3. data: "",
  4. success: function (data) {
  5. template.init(data, function (tpl) {
  6. // TODO
  7. });
  8. }
  9. });
  10. });

上面的代码逻辑上是没有问题的,但是并没有利用好异步I/O带来的优势,这是异步编程的典型问题。

多线程编程

如果是多核CPU,单个Node进程实际没有充分利用多核CPU,浏览器提出了Web workers,通过将javascrit执行与UI渲染分离,可以良好的利用多核CPU。因为前端浏览器对标准的滞后,Web workers并没有广泛应用起来。

异步转同步

习惯同步编程的同学,并不能从容面对异步编程带来的副产品,比如嵌套回调、业务分散。Node 提供了绝大部分异步 API 却很少有同步 API,往往出现同步需求会无所适从,虽然 Node 试图异步转同步但是并没有原生的支持,需要借助库或者编译实现,对于异步编程通过良好的流程控制,还是可以降落几梳理成顺序的形式。

异步编程解决方案 事件发布/订阅模式

</>复制代码

  1. // 订阅
  2. emiiiter.on("event"function(message) {
  3. console.log(message);
  4. })
  5. // 发布
  6. emitter.emit("event", "i am a message");

事件监听是一种高阶函数的应用,通过事件可以把内部数据传递给外部的调用者,编程者可以不用关心组件内部如何执行,只需关注在需要的事件点上即可。注意:

如果事件的监听器过多可能出现过度占用cup的结果。

如果运行期间触发了error事件,解释器会检查是否对error监听了事件,如果有就交给监听器处理,如果没有则将错误抛出。所以应该对error事件做监听。

利用事件可以解决雪崩问题:当大量的访问同时发生时,服务器无法对所有的访问做处理,可以在第一个回调添加状态锁控制服务器的访问数量,同时使用事件(once)把所有请求压入队列中。

promise/deferred模式

promise/A 规定了三种状态,未完成态、完成态和失败态,未完成态向其他两种状态转化,不能逆转;

pedding -> resolved

</>复制代码

  1. -> rejected

</>复制代码

  1. function call(state, fn, err, arg) {
  2. if (state === "pendding") {
  3. fn(arg);
  4. } else {
  5. fn(err);
  6. }
  7. }
  8. new Promise = function (fn) {
  9. this.state = "pendding";
  10. this.fn = function() {};
  11. return fn(this.resolve, this.reject);
  12. }
  13. Promise.prototype.then = function (fn) {
  14. this.fn = fn;
  15. return this;
  16. }
  17. Promise.prototype.resolve = function (arg) {
  18. this.state = "resolved";
  19. call(this.state, this.fn, null, arg);
  20. return this;
  21. }
  22. Promise.prototype.reject = function () {
  23. this.state = "rejected";
  24. var err = "err opened";
  25. call(this.state, this.fn, err);
  26. return this;
  27. }
  28. new Promise(function (resolve, reject) {
  29. setTimeout(function () {
  30. var value = "abc";
  31. resolve(value);
  32. }, 100);
  33. }).then(function (result) {
  34. console.log(result);
  35. });
流程控制库
中间件

使用connect存储中间件手动调用执行的方式,例如next,通常叫做尾触发,尾触发在jquery中非常常见,比如

</>复制代码

  1. $get("/get").success().error();

这种方式首先注册中间件,每个中间件包括传递请求对象,响应对象和尾触发函数,通过队列行程一个处理流,最简单的中间例如:

</>复制代码

  1. function (req, res, err) {
  2. // 中间件
  3. }

connect核心代码:

</>复制代码

  1. function creatServer() {
  2. function app(req, res) {
  3. app.handle(req,res);
  4. }
  5. app.stack = [];
  6. for (var i = 0; i < arguments.length; ++i) {
  7. app.use(arguments[i]);
  8. }
  9. return app;
  10. }

app.use:

</>复制代码

  1. app.use = function(router, fn) {
  2. this.stack.push(fn);
  3. return this;
  4. }

next:

</>复制代码

  1. function handle = function() {
  2. // ...
  3. next();
  4. }
  5. function next() {
  6. // ... next callback ...
  7. layer = this.stack[index++];
  8. layer.handle(req, res, next);
  9. }
async

异步的串行执行

</>复制代码

  1. async.series([function (callback) {
  2. callback();
  3. },function (callback) {
  4. callback();
  5. }], function (err, result) {})

等价于:

</>复制代码

  1. function (callback) {
  2. function (callback) {
  3. callback();
  4. }
  5. callback();
  6. }

异步的并行执行:

</>复制代码

  1. async.parallel([function (callback) {
  2. callback();
  3. }, function (callback) {
  4. callback();
  5. }], function (err, results) {
  6. });

等价于:

</>复制代码

  1. var counter = 2;
  2. var results = [];
  3. var done = function (index, value) {
  4. results[index] = value;
  5. if (!--conuter) {
  6. callback(null, results);
  7. }
  8. }
  9. function (callback) {
  10. // var value = ...
  11. callback();
  12. done(0, value);
  13. }
  14. function (callback) {
  15. // var value = ...
  16. callback();
  17. done(1, value);
  18. }

依赖处理

</>复制代码

  1. 当前一个异步的结果是后一个异步的输入时,async使用waterfall方式处理
  2. async.waterfall([function (callback) {
  3. callback();
  4. }, function (arg1, callback) {
  5. callback();
  6. }, function (arg2, callback) {
  7. callback();
  8. }], function (err, results) {
  9. });

当存在很多依赖关系,有同步有异步时,async使用auto()实现复杂的处理

</>复制代码

  1. async.waterfall({
  2. fun1:function (callback) {
  3. callback();
  4. },
  5. fun2: ["fun1", function (arg1, callback) {
  6. callback();
  7. }, function (arg2, callback) {
  8. callback();
  9. }]}, function (err, results) {
  10. });
step

step接受任意数量的任务,所有任务会串行执行:

</>复制代码

  1. step(task1, task2, task3);

step使用next把上一步的结果传递给下一步作为参数
在执行多个异步任务时,调用代码如下:

</>复制代码

  1. step(function () {
  2. fn1(this.parallel());
  3. fn2(this.parallel());
  4. }, function (err, result1, result2) {
  5. });
wind

wind旨在控制异步流程的逻辑控制,其作用类似generator:

</>复制代码

  1. eval(Wind.compile("async", funtion () {
  2. $await(Wind.Async.sleep(20)); //延迟20ms
  3. console.log("hello world");
  4. }));
generator generaor函数

</>复制代码

  1. function * maker(){
  2. var index = 0;
  3. while (index < 10) {
  4. yield index++;
  5. }
  6. }
  7. var g = maker();
  8. // 输出结果
  9. console.log(g.next().value); // 0
  10. console.log(g.next().value); // 1
  11. console.log(g.next().value); // 2
yeild关键字

yield 关键字用来暂停和恢复一个生成器函数

</>复制代码

  1. [rv] = yield [expression];
  2. yield [[expression]];

rv 返回传递给生成器的 next() 方法的可选值,以恢复其执行。

Regenerator

上面这段代码等价下面代码:

</>复制代码

  1. var _marked = [maker].map(regeneratorRuntime.mark);
  2. function maker() {
  3. var index;
  4. return regeneratorRuntime.wrap(function maker$(_context) {
  5. while (1) {
  6. switch (_context.prev = _context.next) {
  7. case 0:
  8. index = 0;
  9. case 1:
  10. if (!(index < 10)) {
  11. _context.next = 6;
  12. break;
  13. }
  14. _context.next = 4;
  15. return index++;
  16. case 4:
  17. _context.next = 1;
  18. break;
  19. case 6:
  20. case "end":
  21. return _context.stop();
  22. }
  23. }
  24. }, _marked[0], this);
  25. }
  26. var g = maker();
  27. console.log(g.next().value); // 0
  28. console.log(g.next().value); // 1
  29. console.log(g.next().value); // 2

编译机制造了一个状态机,通过_context.next状态的装换完成代码执行的挂起。
假设状态是0 -> n(n是最后一个状态)
0运行第一个yield之前的所有代码,n运行最后一个yield函数之后的所有代码,generator的next尾调用通过一个while循环实现,如果_context.next到达最后一个case就退出循环,等待下一次next调用

regenerator是用来生成generetor函数并返回一个迭代器供外界调用的高阶函数,功能主要是

regenerator-transform: 重写generator函数把yield重写成switch case,并且创建_context.next保存上下文环境;

包装generator函数被返回一个迭代器对象;

经过wrap返回的迭代器:

</>复制代码

  1. GeneratorFunctionPrototype {
  2. _invoke: function invoke(method, arg) { … }
  3. __proto__: GeneratorFunctionPrototype {
  4. constructor: function GeneratorFunctionPrototype() {},
  5. next: function (arg) { … },
  6. throw: function (arg) { … }
  7. }
  8. }

当调用迭代器对象iter.next()方法时,因为有如下代码,所以会执行_invoke方法,而根据前面wrap方法代码可知,最终是调用了迭代器对象的 makeInvokeMethod (innerFn, self, context); 方法

makeInvokeMethod方法内容较多,这里选取部分分析。

</>复制代码

  1. function makeInvokeMethod(innerFn, self, context) {
  2. var state = GenStateSuspendedStart;
  3. return function invoke(method, arg) {

makeInvokeMethod返回invoke函数,当我们执行.next方法时,实际调用的是invoke方法中的下面语句

</>复制代码

  1. var record = tryCatch(innerFn, self, context);

这里tryCatch方法中fn为经过转换后的example$方法,arg为上下文对象context,因为invoke函数内部对context的引用形成闭包引用,所以context上下文得以在迭代期间一直保持。

</>复制代码

  1. function tryCatch(fn, obj, arg) {
  2. try {
  3. return { type: "normal", arg: fn.call(obj, arg) };
  4. } catch (err) {
  5. return { type: "throw", arg: err };
  6. }
  7. }

tryCatch方法会实际调用 example$ 方法,进入转换后的switch case,执行代码逻辑。如果得到的结果是一个普通类型的值,我们将它包装成一个可迭代对象格式,并且更新生成器状态至GenStateCompleted或者GenStateSuspendedYield

</>复制代码

  1. var record = tryCatch(innerFn, self, context);
  2. if (record.type === "normal") {
  3. // If an exception is thrown from innerFn, we leave state ===
  4. // GenStateExecuting and loop back for another invocation.
  5. state = context.done
  6. ? GenStateCompleted
  7. : GenStateSuspendedYield;
  8. var info = {
  9. value: record.arg,
  10. done: context.done
  11. };

伪代码:

</>复制代码

  1. function wrap(innerFn, outerFn, self, tryLocsList) {
  2. var protoGenerator = outerFn && outerFn.prototype instanceof Generator
  3. ? outerFn
  4. : Generator;
  5. var generator = Object.create(protoGenerator.prototype);
  6. var context = new Context(tryLocsList || []);
  7. generator._invoke = makeInvokeMethod(innerFn, self, context);
  8. return generator;
  9. }
  10. function makeInvokeMethod(innerFn, self, context) {
  11. var obj = this;
  12. return function invoke(method, arg) {
  13. context.method = method;
  14. // 把next带入的arg参数赋值给sent
  15. if (context.method === "next") {
  16. context.sent = context._sent = context.arg;
  17. }
  18. // 实际上调用了mark$,并且带入了context
  19. var record = {
  20. arg: innerFn.call(obj, context)
  21. };
  22. // 返回一个可以迭代的对象
  23. return {value: record.arg, done: context.done};
  24. };
  25. }
  26. // 用一个next调用invoke, 如果要进行下一步就传入next
  27. generator.next = next(arg) {
  28. generator._invoke("next", arg);
  29. }
cojs处理generator过程
thunk函数

能够得到一个函数的函数叫thunk函数, thunk函数是一个偏函数,它只带一个执行参数

</>复制代码

  1. function getThunk(number) {
  2. return function (fn) {
  3. setTimeout(() => {
  4. if (number) {
  5. fn(null, number);
  6. } else {
  7. const err = "error open";
  8. fn(err);
  9. }
  10. }, number)
  11. }
  12. }
cojs-generator的自动执行器

</>复制代码

  1. import co from "co";
  2. co(function * () {
  3. var a = yield getThunk(100);
  4. var b = yield getThunk(1000);
  5. console.log("a:", a);
  6. console.log("b:", b);
  7. return [a, b];
  8. })
  9. // 输出
  10. // a 100
  11. // b 1000
cojs代码解析

</>复制代码

  1. function co2Thunk(fn) {
  2. return (done) => {
  3. const ctx = this;
  4. const g = fn.call(ctx);
  5. function next(err, res) {
  6. console.log("next1", res);
  7. let it = g.next(res);
  8. if (it.done) {
  9. done.call(ctx, err, it.value);
  10. } else {
  11. it.value(next);
  12. }
  13. }
  14. next();
  15. }
  16. }
  17. co2Thunk(function * () {
  18. var a = yield getThunk(10000);
  19. var b = yield getThunk(1000);
  20. // console.log("a:", a);
  21. // console.log("b:", b);
  22. return [a, b];
  23. })(function (err, args) {
  24. console.log("callback thunk co : ==========");
  25. // console.log(err, args);
  26. });

co2Thunk的代码等价于:

</>复制代码

  1. function co2Thunk(fn) {
  2. return (done) => {
  3. const ctx = this;
  4. const g = fn.call(ctx);
  5. let it0 = g.next();
  6. it0.value((err, res) => {
  7. const it1 = g.next(res); // 第一次迭代返回的是getThunk(10000);
  8. it0.value((err, res) => {
  9. const it1 = g.next(res); // 第二次迭代返回的是getThunk(1000);
  10. it1.value((err, res) => {
  11. const it2 = g.next(data);
  12. // ...
  13. });
  14. });
  15. });
  16. }
  17. }
  18. // it.value 等价于:
  19. function (fn) {
  20. setTimeout(() => {
  21. if (number) {
  22. fn(null, number);
  23. } else {
  24. const err = "error open";
  25. fn(err);
  26. }
  27. }, number)
  28. }
promise版

</>复制代码

  1. function co2Promise(fn) {
  2. return new Promise((resolve, reject) => {
  3. const ctx = this;
  4. const g = fn.call(ctx);
  5. function next(err, res) {
  6. let it = g.next(res);
  7. if (it.done) {
  8. resolve(it.value);
  9. } else {
  10. it.value(next);
  11. }
  12. }
  13. next();
  14. });
  15. }
  16. co2Promise(function * () {
  17. var a = yield getThunk(100);
  18. var b = yield getThunk(1000);
  19. console.log("a:", a);
  20. console.log("b:", b);
  21. return [a, b];
  22. }).then(function (args) {
  23. console.log("callback promise co : ==========");
  24. console.log(args);
  25. });
thunk升级版

</>复制代码

  1. function co2Thunk(fn) {
  2. return (done) => {
  3. const ctx = this;
  4. const g = fn.call(ctx);
  5. function next(err, res) {
  6. let it = g.next(res);
  7. if (it.done) {
  8. done.call(ctx, err, it.value);
  9. } else {
  10. // 增加对其他类型的处理
  11. const value = toThunk.call(ctx, it.value);
  12. // 对于promise 此处应该是 value.then(next)
  13. value(next);
  14. }
  15. }
  16. next();
  17. }
  18. }
  19. co2Thunk(function * () {
  20. var a = getThunk(100);
  21. var b = getThunk(1000);
  22. // console.log("a:", a); console.log("b:", b);
  23. return yield [a, b];
  24. })(function (err, args) {
  25. console.log("callback thunk co : ==========");
  26. console.log(err, args);
  27. });
  28. function toThunk(obj) {
  29. if (isObject(obj) || isArray(obj)) {
  30. return objectToThunk(obj);
  31. }
  32. if (isPromise(obj)) {
  33. return promiseToThunk.call(ctx, obj);
  34. }
  35. return obj;
  36. }
  37. function objectToThunk(obj) {
  38. return function (done) {
  39. let keys = Object.keys(obj);
  40. let length = keys.length;
  41. let results = new obj.constructor();
  42. for(let key in keys) {
  43. const fn = toThunk(obj[key]);
  44. fn((err, res) => {
  45. results[key] = res;
  46. --length || done(null, results);
  47. }, key);
  48. }
  49. }
  50. }
  51. function promiseToThunk(promise){
  52. return function(done){
  53. promise.then(function(err,res){
  54. done(err,res);
  55. },done)
  56. }
  57. }
  58. function isObject(obj) {
  59. return obj && Object == obj.constructor;
  60. }
  61. function isArray(obj) {
  62. return Array.isArray(obj);
  63. }
  64. function isPromise(obj) {
  65. return obj && "function" == typeof obj.then;
  66. }
async/await

</>复制代码

  1. async function fn(args){
  2. // ...
  3. }

等同于

</>复制代码

  1. function fn(args){
  2. return co2Thunk(function*() {
  3. // ...
  4. });
  5. }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84625.html

相关文章

  • js异步解决方案 --- 回调函数 vs promise vs generater/yield vs

    摘要:异步流程管理说白了就是为了解决回调地狱的问题。对象代表一个异步操作,有三种状态进行中已成功和已失败。如果改变已经发生了,你再对对象添加回调函数,也会立即得到这个结果。执行函数后返回的是一个遍历器对象,可以依次遍历函数内部的每一个状态。 javascript -- 深度解析异步解决方案 高级语言层出不穷, 然而唯 js 鹤立鸡群, 这要说道js的设计理念, js天生为异步而生, 正如布道...

    0xE7A38A 评论0 收藏0
  • js学习之异步处理

    摘要:学习开发,无论是前端开发还是都避免不了要接触异步编程这个问题就和其它大多数以多线程同步为主的编程语言不同的主要设计是单线程异步模型。由于异步编程可以实现非阻塞的调用效果,引入异步编程自然就是顺理成章的事情了。 学习js开发,无论是前端开发还是node.js,都避免不了要接触异步编程这个问题,就和其它大多数以多线程同步为主的编程语言不同,js的主要设计是单线程异步模型。正因为js天生的与...

    VioletJack 评论0 收藏0
  • ES6中的异步编程Generators函数+Promise:最强大的异步处理方式

    摘要:更好的异步编程上面的方法可以适用于那些比较简单的异步工作流程。小结的组合目前是最强大,也是最优雅的异步流程管理编程方式。 访问原文地址 generators主要作用就是提供了一种,单线程的,很像同步方法的编程风格,方便你把异步实现的那些细节藏在别处。这让我们可以用一种很自然的方式书写我们代码中的流程和状态逻辑,不再需要去遵循那些奇怪的异步编程风格。 换句话说,通过将我们generato...

    Taonce 评论0 收藏0
  • 探索Javascript 异步编程

    摘要:因为浏览器环境里是单线程的,所以异步编程在前端领域尤为重要。除此之外,它还有两个特性,使它可以作为异步编程的完整解决方案函数体内外的数据交换和错误处理机制。 showImg(https://segmentfault.com/img/bVz9Cy); 在我们日常编码中,需要异步的场景很多,比如读取文件内容、获取远程数据、发送数据到服务端等。因为浏览器环境里Javascript是单线程的,...

    Salamander 评论0 收藏0
  • Javascript中的Generator函数和yield关键字

    摘要:序在中,大家讨论的最多的就是异步编程的操作,如何避免回调的多次嵌套。今天所讲的和就是和异步编程有关,可以帮助我们把异步编程同步化。然而这样的方法依然需要依赖外在的库函数,于是中提出了和关键字。 序 在Javascript中,大家讨论的最多的就是异步编程的操作,如何避免回调的多次嵌套。异步操作的回调一旦嵌套很多,不仅代码会变的臃肿,还很容易出错。各种各样的异步编程解决方案也被不断提出,例...

    ZHAO_ 评论0 收藏0
  • ES6&ES7中的异步Generator函数与异步编程

    摘要:传统的异步方法回调函数事件监听发布订阅之前写过一篇关于的文章,里边写过关于异步的一些概念。内部函数就是的回调函数,函数首先把函数的指针指向函数的下一步方法,如果没有,就把函数传给函数属性,否则直接退出。 Generator函数与异步编程 因为js是单线程语言,所以需要异步编程的存在,要不效率太低会卡死。 传统的异步方法 回调函数 事件监听 发布/订阅 Promise 之前写过一篇关...

    venmos 评论0 收藏0

发表评论

0条评论

AbnerMing

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<