资讯专栏INFORMATION COLUMN

[前端漫谈_2] 从 Dva 的 Effect 到 Generator + Promise 实现异步

pekonchan / 2680人阅读

摘要:你能学到什么如何使用实现异步编程异步编程的原理解析前言结合上一篇文章,我们来聊聊基础原理说到异步编程,你想到的是和,但那也只是的语法糖而已。表示一个异步操作的最终状态完成或失败,以及其返回的值。至此实现异步操作的控制。

你能学到什么

如何使用 Generator + Promise 实现异步编程

异步编程的原理解析

前言

结合 上一篇文章 ,我们来聊聊 Generator

基础原理

说到异步编程,你想到的是asyncawait ,但那也只是 Generator 的语法糖而已。dva 中有一个 Effect 的概念,它就是使用 Generator 来解决异步请求的问题,我们也来聊一聊 Generator + Promise 如何异步编程:

开始之前,我们需要了解一些基本的概念:

Generator作为 ES6 中使用协程的解决方案来处理异步编程的具体实现,它的特点是: Generator 中可以使用 yield 关键字配合实例 gen 调用 next() 方法,来将其内部的语句分割执行。 简言之 : next() 被调用一次,则 yield 语句被执行一句,随着 next() 调用, yield 语句被依次执行。

Promise表示一个异步操作的最终状态(完成或失败),以及其返回的值。参考Promise-MDN

所以,异步编程使用 GeneratorPromise 来实现的原理是什么呢?

因为 Generator 本身 yield 语句是分离执行的,所以我们利用这一点,在 yield 语句中返回一个 Promise 对象

首次调用 Generator 中的 next() 后, 假设返回值叫 result ,那么此时 result.value 就是我们定义在 yield 语句中的 Promise 对象

注意:在这一步,我们已经把原来的执行流程暂停,转而执行 Promise 的内容,已经实现了控制异步代码的执行,因为此时我们如果不继续执行 next()generator 中位于当前被执行的 yield 后面的内容,将不会继续执行,这已经达到了我们需要的效果

接下来我们就是在执行完当前 Promise 之后,让代码继续往下执行,直到遇到下一个 yield 语句:

这一步是最关键的 所以我们怎么做呢:

步骤1: 在当前的 Promisethen() 方法中,继续执行 gen.next()

步骤2: 当 gen.next() 返回的结果 result.done === true 时,我们拿到 result.value【也就是一个新的 Promise 对象】再次执行并且在它的then() 方法中继续上面的步骤1,直至 result.done === false 的时候。这时候调用 resolve() 使 promise 状态改变,因为所有的 yield 语句已经被执行完。

步骤1 保证了我们可以走到下一个 yield 语句

步骤2 保证了下一个 yield 语句执行完不会中断,直至 Generator 中的最后一个 yield 语句被执行完。

流程示意图:

具体实现
co 是著名大神 TJ 实现的 Generator 的二次封装库,那么我们就从co库中的一个demo开始,了解我们的整个异步请求封装实现:
co(function*() {
    yield me.loginAction(me.form);
    ...
});

在这里我们引入了co库,并且用co来包裹了一个generator(生成器)对象。

接下来我们看下co对于包裹起来的generator做了什么处理

function co(gen) {
  // 1.获取当前co函数的执行上下文环境,获取到参数列表
  var ctx = this;
  var args = slice.call(arguments, 1);
  // 2.返回一个Promise对象
  return new Promise(function(resolve, reject) {
    //  判断并且使用ctx:context(上下文环境)和arg:arguments(参数列表)初始化generator并且复制给gen
    // 注意:
    // gen = gen.apply(ctx, args)之后
    // 我们调用 gen.next() 时,返回的是一个指针,实际的值是一个对象
    // 对象的形式:{done:[false | true], value: ""}
    if (typeof gen === "function") gen = gen.apply(ctx, args);
    // 当返回值不为gen时或者gen.next的类型不为function【实际是判断是否为generator】时
    // 当前promise状态被设置为resolve而结束
    if (!gen || typeof gen.next !== "function") return resolve(gen);
    // 否则执行onFulfilled()
    onFulfilled();
  });
}

总结一下这里发生了什么

返回一个 promise

promise 中将被包裹的 generator 实例化为一个指针,指向 generator 中第一个 yield 语句

判断 generator 实例化出来的指针是否存在:如果没有 yield 语句则指针不存在
判断指针 gen.next() 方法是否为 function :如果不为 function 证明无法执行 gen.next()
条件有一项不满足就将 promise 的状态置为 resolve
否则执行 onFulfilled()

接下来我们看下 onFulfilled() 的实现

    function onFulfilled(res) {
      // 在执行onFulfilled时,定义了一个ret来储存gen.next(res)执行后的指针对象
      var ret;
      try {
        ret = gen.next(res);
      // 在这里,yield语句抛出的值就是{value:me.loginAction(me.form), done:false}
      } catch (e) {
        return reject(e);
      }
    // 将ret对象传入到我们定义在promise中的next方法中
      next(ret);
      return null;
    }

总结一下,onFulfilled 最主要的工作就是

执行 gen.next() 使代码执行到 yield 语句

将执行后返回的结果传入我们自定义的 next() 方法中

那么我们再来看 next() 方法

    function next(ret) {
    // 进入next中首先判断我们传入的ret的done状态:
    // 情况1:ret.done = true 代表我们这个generator中所有yield语句都已经执行完。
    // 那么将ret.value传入到resolve()中,promise的状态变成解决,整个过程结束。
      if (ret.done) return resolve(ret.value);
    // 情况2:当前ret.done = false 代表generator还未将所有的yield语句执行完,那么这时候
    // 我们把当前上下文和ret.value传入toPromise中,将其转换为对应的Promise对象`value`
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
    // 当value确实是一个promise对象的时候,return value.then(onFulfilled,onRejected)
    // 我们重新进入到了generator中,执行下一条yield语句
      return onRejected(new TypeError("You may only yield a function, promise, generator, array, or object, "
        + "but the following object was passed: "" + String(ret.value) + """));
    }

总结一下,next 主要工作

判断上一次 yield 语句的执行结果

yieldresultvalue 值【其实就是我们要异步执行的 Promise

执行 valuethen 方法,重新进入到 onFulfilled 方法中,而在 onFulfilled 中,我们又将进入到当前方法,如此循环的调用,实现了 generatorPromise 的执行切换,从而实现了 Promise 的内容按照我们所定义的顺序执行。

有同学可能对这里的 toPromise 方法有一些疑惑,我先把代码贴出来

function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  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;
}

其实这个函数做的事情就是,根据不同的类型进行转换,使得最后输出的类型都是一个 Promise。那具体的转换细节,大家可以参考co库的源码。

至此实现异步操作的控制。

最后

这里是 Dendoink ,奇舞周刊原创作者,掘金 [联合编辑 / 小册作者] 。
对于技术人而言:技 是单兵作战能力,术 则是运用能力的方法。得心应手,出神入化就是 艺 。在前端娱乐圈,我想成为一名出色的人民艺术家。扫码关注公众号 前端恶霸 我在这里等你:

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

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

相关文章

  • [前端漫谈_1] for of 聊 Generator

    摘要:数据的层级意味着迭代数据结构并提取它的数据。对于技术人而言技是单兵作战能力,术则是运用能力的方法。在前端娱乐圈,我想成为一名出色的人民艺术家。 聊聊 for of 说起 for of 相信每个写过 JavaScript 的人都用过 for of ,平时我们用它做什么呢?大多数情况应该就是遍历数组了,当然,更多时候,我们也会用 map() 或者 filer() 来遍历一个数组。 但是就...

    miqt 评论0 收藏0
  • redux-saga框架使用详解及Demo教程

    摘要:通过创建将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替中间件。 redux-saga框架使用详解及Demo教程 前面我们讲解过redux框架和dva框架的基本使用,因为dva框架中effects模块设计到了redux-saga中的知识点,可能有的同学们会用dva框架,但是对redux-saga又不是很熟悉,今天我们就来简单的讲解下saga框架的主要API和如何配合redux框...

    Nosee 评论0 收藏0
  • React生态,dva源码阅读

    摘要:下面会从浅到深,淡淡在阅读源码过程中自己的理解。分拆子页面后,每一个子页面对应一个文件。总结上面就是最早版本的源码,很简洁的使用了等其目的也很简单简化相关生态的繁琐逻辑参考源码地址   dva的思想还是很不错的,大大提升了开发效率,dva集成了Redux以及Redux的中间件Redux-saga,以及React-router等等。得益于Redux的状态管理,以及Redux-saga中...

    bergwhite 评论0 收藏0

发表评论

0条评论

pekonchan

|高级讲师

TA的文章

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