使用Generator函数进行异步编程

Generator函数在工作中还没有用到过,一直在使用async,最近在看async的原理,发现它只是Generator的语法糖。

Generator的基础知识之前写过文章介绍过(https://www.cnblogs.com/wangtingnoblog/p/js_Generator.html),这里主要讨论一下怎么使用Generator函数来进行异步编程。

先来看下面的代码:

1 function* g() {
2   console.log(‘g start‘)
3   yield setTimeout(() => console.log(‘setTimeout‘), 3000)
4   console.log(‘g end‘)
5 }
6 const useg = g();
7 useg.next();
8 useg.next();

运行结果:

分析:

  • 第7步时,yield后面的异步操作已经开始,但是还没有结束,会在3秒后打印出setTimeout,
  • 因为没有等到异步操作结束就调用了next方法,造成先打印出 g end,然后才打印出setTimeout
  • 我们想要的是按照正常的顺序进行打印,比如在得知yield完成之后才能结束函数,这个要怎么实现呢?
  • 如果在yield表达式执行完成后能执行一个回调函数,我们在回调函数中再通知Generator函数执行下一步就可以了。

看下面的代码:

 1 function* g() {
 2   console.log(‘g start‘)
 3   yield function(callBack) {
 4     setTimeout(() => {
 5       console.log(‘setTimeout‘)
 6       callBack()
 7     }, 3000)
 8   }
 9   console.log(‘g end‘)
10 }
11 const useg = g();
12 const first = useg.next();
13 first.value(() => useg.next());

运行结果:

分析:

  • 可以看到,我们把异步操作封装在一个函数中,这个函数只接受一个回调函数作为参数,当异步操作执行完成会,它会调用回调函数。
  • 这样就保证Generator函数的下部分的代码一定在上部分的代码执行完成之后才执行。
  • 有一点需要注意的是,这样修改代码使得异步操作变成了惰性执行了。
  • 之前的代码是只要调用useg.next方法,异步操作就会开始执行,修改后的代码则变成就算调用了useg.next方法,如果没有调用value(callback),异步操作也不会执行。
  • 这种惰性执行和Observable很像,如果没有订阅Observable,Observable的逻辑就不会执行
  • Promise是立即执行的

我们再来看一下实际的例子,使用Generator函数封装ajax:

 1 function* g() {
 2   console.log(‘g start‘)
 3   const userName = yield function(callBack) {
 4     const xhr = new XMLHttpRequest();
 5     xhr.open(‘get‘, ‘http://localhost:3002/users?id=1‘, true);
 6     xhr.responseType = ‘json‘;
 7     xhr.setRequestHeader(‘Accept‘, ‘application/json‘);
 8     xhr.onload = () => {
 9       const data = xhr.response;
10       callBack(data[0].name);
11     }
12     xhr.send(null);
13   }
14   const families = yield function(callBack) {
15     const xhr = new XMLHttpRequest();
16     xhr.open(‘get‘, `http://localhost:3002/family?name=${userName}`, true);
17     xhr.responseType = ‘json‘;
18     xhr.setRequestHeader(‘Accept‘, ‘application/json‘);
19     xhr.onload = () => {
20       const data = xhr.response;
21       callBack(data[0].families);
22     }
23     xhr.send(null);
24   }
25   console.log(families)
26   console.log(‘g end‘)
27 }
28 const useg = g();
29 const first = useg.next();
30 first.value(
31   (name) => {
32     const second = useg.next(name)
33     second.value(
34       (families) => useg.next(families)
35     )
36   }
37 );

上面的代码执行的是:先通过id取得用户名,再通过用户名取得家庭成员。只看第1行到第27行,步骤清晰,很像是同步的写法。

我们需要关注的是,第28行到第37行对Generator函数的流程管理:

  1. 调用Generator函数取得迭代器对象
  2. 调用next方法取得value属性
  3. 调用value属性的值,给它传入回调函数
  4. 在回调函数中执行第2步,第3步
  5. 判断此时函数已经执行完成

注意到这里的12345步是可以用迭代来进行的,修改流程管理的代码如下:

 1 function run(g) {
 2   // 取得迭代器
 3   const useg = g();
 4   // 定义循环调用的函数
 5   function loop(data) {
 6     // 取得当前的值
 7     const result = useg.next(data);
 8     // 判断是否已经执行完成,执行完成会退出
 9     if (result.done) return;
10     // 没有执行完成则继续执行回调
11     result.value(loop);
12   }
13   // 开始迭代
14   loop();
15 }
16 run(g);

可以看到,使用迭代来管理流程时完全没有必要知道生成器的内部结构。而且这种方式也不管生成器中有多少个yield。

有一点需要注意的是,使用这种发生来管理流程要求生成器中yield后面的表达式必须是一个函数,且这个函数有唯一参数(回调函数)

其实,我们需要的只是在异步操作有了结果之后把执行权再交还给Generator函数继续执行。

问题是怎么知道异步操作有了结果?

  1. 回调函数
  2. Promise

上面已经介绍过使用回调函数来控制流程,它的限制是yield后面的表达式必须是一个以回调函数为参数的函数。

下面介绍使用Promise来控制流程:

ajax函数返回Promise

 1 function ajax(url) {
 2   return new Promise(
 3     (resolve, reject) => {
 4       const xhr = new XMLHttpRequest();
 5       xhr.open(‘get‘, url, true);
 6       xhr.responseType = ‘json‘;
 7       xhr.setRequestHeader(‘Accept‘, ‘application/json‘);
 8       xhr.onload = () => {
 9         const data = xhr.response;
10         resolve(data);
11       }
12       xhr.send(null);
13     }
14   )
15 }

 1 function* g() {
 2   console.log(‘g start‘)
 3   const userNameResponse = yield ajax(‘http://localhost:3002/users?id=1‘)
 4   const familiesResponse = yield ajax(‘http://localhost:3002/family?name=‘ + userNameResponse[0].name)
 5   console.log(familiesResponse[0].families)
 6   console.log(‘g end‘)
 7 }
 8 const useg = g();
 9 useg.next().value.then(
10   (userNameResponse) => useg.next(userNameResponse).value.then(
11     (familiesResponse) => useg.next(familiesResponse)
12   )
13 );

可以看到:

  1. yield后面必须是Promise对象
  2. 流程控制中同样可以使用迭代来执行

Promise迭代版本的流程控制:

function run(g) {
  // 取得迭代器
  const useg = g();
  // 定义循环调用的函数
  function loop(data) {
    // 当前的值
    const result = useg.next(data);
    // 判断是否已经执行完成,执行完成会退出
    if (result.done) return;
    // 没有执行完成则继续执行回调
    result.value.then(loop);
  }
  // 开始迭代
  loop();
}
run(g);

对比之前的使用回调函数来控制流程的代码,你会发现和使用Promise来控制流程的代码如此的类似。

使用Promise来控制流程需要注意的是yield后面必须是一个Promise对象

对比使用回调函数控制流程和使用Promise对象来控制流程

  • 使用回调函数控制流程需要yield后面是一个以回调函数为参数的函数,使用Promise对象来控制流程需要yield后面必须是Promise对象。
  • 使用回调函数控制流程时,异步操作是惰性执行的,使用Promise对象来控制流程时,异步操作是立即执行的

总结:

Generator函数本身使得异步操作看起来非常像同步操作,麻烦的是它的流程控制需要我们手动调用。(之后要讨论的async就是对Generator的进一步封装,不用开发者总结来流程控制)

另外上面的例子为了简化,都没有做异常处理,实际开发中,异常处理还是很有必要的。

2019-02-10

参考:《ES6标准入门》、《Learning TypeScript》、《深入理解ES6》

原文地址:https://www.cnblogs.com/wangtingnoblog/p/js_Generator_async.html

时间: 2024-08-29 23:29:49

使用Generator函数进行异步编程的相关文章

js-ES6学习笔记-Generator函数的异步应用

1.ES6 诞生以前,异步编程的方法,大概有下面四种. 回调函数 事件监听 发布/订阅 Promise 对象 Generator 函数将 JavaScript 异步编程带入了一个全新的阶段. 2.所谓"异步",简单说就是一个任务不是连续完成的,可以理解成该任务被人为分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段. 3.JavaScript 语言对异步编程的实现,就是回调函数.所谓回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候

Generator 函数的异步应用

异步编程对 JavaScript 语言太重要.Javascript 语言的执行环境是"单线程"的,如果没有异步编程,根本没法用,非卡死不可.本章主要介绍 Generator 函数如何完成异步操作. 传统方法 ES6 诞生以前,异步编程的方法,大概有下面四种. 回调函数 事件监听 发布/订阅 Promise 对象 Generator 函数将 JavaScript 异步编程带入了一个全新的阶段. 基本概念 异步 所谓"异步",简单说就是一个任务不是连续完成的,可以理解成

JavaScript的异步编程

JavaScript有几种异步编程的解决方案. 一.回调函数 被传递给其他函数的函数叫作回调函数.回调函数把任务的第二段单独写在一个函数中,待重新执行这个任务时直接调用这个回调函数. Node中文件操作经常有这样的应用. 使用回调函数时,如果只有一个回调,回调中不会包含其余的回调函数也还好,但是如果回调中包含回调,就会造成所谓的回调地狱,十分不利于代码的review和debug 二.事件监听 事件监听把事件的发生源和事件的发生后的操作进行了分离. 比如ajax中对于load事件和error事件的

Generator函数异步应用

转载请注明出处: Generator函数异步应用 上一篇文章详细的介绍了Generator函数的语法,这篇文章来说一下如何使用Generator函数来实现异步编程. 或许用Generator函数来实现异步会很少见,因为ECMAScript 2016的async函数对Generator函数的流程控制做了一层封装,使得异步方案使用更加方便. 但是呢,我个人认为学习async函数之前,有必要了解一下Generator如何实现异步,这样对于async函数的学习或许能给予一些帮助. 文章目录 知识点简单回

Generator 函数的语法

简介 § ? 基本概念 Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同.本章详细介绍 Generator 函数的语法和 API,它的异步编程应用请看<Generator 函数的异步应用>一章. Generator 函数有多种理解角度.语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态. 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的

JavaScript中的Generator函数

1. 简介 Generator函数时ES6提供的一种异步编程解决方案.Generator语法行为和普通函数完全不同,我们可以把Generator理解为一个包含了多个内部状态的状态机. 执行Generator函数回返回一个遍历器对象,也就是说Generator函数除了提供状态机,还是一个遍历器对象生成函数.Generator可以以此返回多个遍历器对象,通过这个对象可以以此访问到Generator函数内部的多个状态. 形式上Generator函数和普通的函数有两点不同,一是function关键字后面

深入解析js异步编程利器Generator

我们在编写Nodejs程序时,经常会用到回调函数,在一个操作执行完成之后对返回的数据进行处理,我简单的理解它为异步编程. 如果操作很多,那么回调的嵌套就会必不可少,那么如果操作非常多,那么回调的嵌套就会变得让人无法忍受了. 我们知道的Promises就是问了解决这个问题而提出来的.然而,promises并不是一种新的功能,它只是一种新的写法,原来横向发展的回调函数,被排成了队竖向发展. 然而,Generator不同,它是一种新的解决方案. 文章中提到的所有代码都可以在这里找到源码:[查看源码].

Javascript异步编程之二回调函数

上一节讲异步原理的时候基本上把回掉函数也捎带讲了一些,这节主要举几个例子来具体化一下.在开始之前,首先要明白一件事,在javascript里函数可以作为参数进行传递,这里涉及到高阶函数的概念,大家可以自行google一下. 传统的同步函数需要返回一个结果的话都是通过return语句实现,例如: function foo() { var a = 3, b = 2; return a+b; } var c = foo(); console.log(c); //5 就是说后面的代码console.lo

关于generator异步编程的理解以及如何动手写一个co模块

generator出现之前,想要实现对异步队列中任务的流程控制,大概有这么一下几种方式: 回调函数 事件监听 发布/订阅 promise对象 第一种方式想必大家是最常见的,其代码组织方式如下: function fn(url, callback){ var httpRequest; //创建XHR httpRequest = window.XMLHttpRequest ? new XMLHttpRequest() : window.ActiveXObject ? new ActiveXObjec