美文网首页react
Javascript 中通过 yield 和 promise 使

Javascript 中通过 yield 和 promise 使

作者: zijing07 | 来源:发表于2017-04-30 00:44 被阅读15085次

    背景

    由于最近一段时间一直在用react-native进行APP的开发,所以接触了不少 javascript

    在我们第一次使用react-native + redux + saga开发的过程中,学习、见识到了不少javascript神奇的功能,比如在使用saga的过程中用到了 yield,并且对于其使得异步操作同步化十分好奇,就进行了一番探索。

    yield简单介绍

    先看一段简单的代码

    function* gen() {
      yield console.log(1)
      yield console.log(2)
      console.log(3)
    }
    
    const g = gen()
    g.next()
    g.next()
    g.next()
    

    输出如下

    1
    2
    3
    

    函数gen的声明使用了function*,使得gen函数成为一个generator,并且可以在其中里面使用yield关键字,gen()返回一个generator对象,通过next()依次调用。

    在我的理解看来,可以将yield关键字理解为函数的断点,每次next()就会从上次的断点(yield)执行到下次的断点(yield),直到函数结束退出,于是就产生了上面的结果。

    next() 的返回值

    修改一下上面程序的输出代码,用来查看一下next()函数的返回值

    const g = gen()
    console.log(g.next())
    console.log(g.next())
    console.log(g.next())
    

    输出:

    1
    { value: undefined, done: false }
    2
    { value: undefined, done: false }
    3
    { value: undefined, done: true }
    

    可以看到,next()函数的返回值是一个{value: any, done: boolean}objectvalue是运行到这个断点的返回值,done表示该函数是否已经执行完毕,对next()方法返回值的了解,会有助于我们下面的程序实现。

    promise 的简单了解

    promise的出现,是为了解决javascript中的异步无限回调,一般的使用方法如下:

    function delay(ms) {  
      return new Promise((resolve, reject) => {
        setTimeout(resolve, ms);
      });
    }
    
    console.log(1)
    delay(300).then(() => console.log(2))
    console.log(3)
    

    输出:

    1
    3
    2
    

    delay()函数中使用setTimeout()来模拟一个异步操作,再用promise封装一下,使得可以使用promisethen()方法来在异步操作完成之后,执行特定的代码。

    现在promise的使用已经很普遍,javascript标准中的fetch()函数也是支持promise的回调机制,以方便开发者更容易的处理网络请求的异步返回。

    异步操作 - 问题的产生

    在我们的使用过程中,有类似如下结构的代码:

    function* baum() {
      yield delay(300).then(() => console.log(1))
      yield console.log(2)
      yield delay(300).then(() => console.log(3))
      yield console.log(4)
    }
    
    const b = baum()
    b.next()
    b.next()
    b.next()
    b.next()
    

    输出如下:

    2
    4
    1
    3
    

    函数baum()结构表达的意思是,有一些同步的操作,然后会发出异步的请求(比如网络请求),异步请求结束后,再执行后面的代码。但是因为delay()函数的异步使得13的输出延迟了,并没有达到预期效果。

    可是令人十分费解的是,在saga中,这样的程序结构,是会按照顺序执行的效果呈现出来,即输出是1,2,3,4,所以一定是saga在对诸如baum()这样的generator进行了一层包裹,使得里面的同步操作可以等待上一个异步promise函数执行完成后再被触发。

    异步变同步 - 机械的实现

    为了可以使得代码的执行顺序变成函数里面的书写顺序,我们先看一下baum()函数每次next()的返回值都是什么,对上一节的输出代码稍作改写:

    const b = baum()
    console.log(b.next())
    

    输出:

    { value: Promise { <pending> }, done: false }
    1
    

    上面第一行输出中的value值,是delay(300).then(() => console.log(1))这段代码的执行结果,那么我们可知,对于一个promise,它的then()函数的返回值同样是一个promise对象。由此来说,只要将同步的next()执行,放到它前面异步的promise中的then()函数里,即可以达到同步代码发生在异步代码操作之后的效果了。

    说起来有点绕,看一下改进之后的代码:

    const b = baum()
    b.next().value.then(() => {
      b.next()
    })
    

    输出:

    1
    2
    

    果然!如之前预料的一样的执行效果,那么把这段代码补全,即可以达到顺序输出1,2,3,4的效果了:

    const b = baum()
    b.next().value.then(() => { // 第一个 delay 函数返回的 promise
      b.next()
      b.next().value.then(() => { // 第二个 delay 函数返回的 promise
        b.next()
      })
    })
    

    输出:

    1
    2
    3
    4
    

    现在已经通过promisethen()方法,做到了异步、同步代码执行时的所见即所得,即程序的输出顺序,是和书写顺序一致的。

    那么最后的任务,就是对上面的代码进行封装,以免去这种手工机械化的调用。

    异步变同步 - 自动化的实现

    经过一番折腾,最后写出了下面一个函数himmel()来使得generator中的调用,无论异步的还是同步的yield操作,都是依照着代码的书写顺序执行的:

    function himmel(gen) {
      const item = gen.next()
      if (item.done) {
        return item.value
      }
    
      const { value, done } = item
      if (value instanceof Promise) {
        value.then((e) => himmel(gen))
      } else {
        himmel(gen)
      }
    }
    

    函数的实现是一个递归,接收参数是一个generator实例,退出条件即为当yield结果中的donetrue的时候。后面的代码会判断value是否是一个promise,如果是的话,就在then()方法中递归,否则就认为是同步代码,直接递归。

    测试一下:

    himmel(baum())
    

    输出:

    1
    2
    3
    4
    

    结果也是正确的。

    验证以上思路的可行性

    yield出现的时候,就随之出现了一个比较有名的库叫作 co ,这个库的作用就是控制同步与异步代码的执行顺序,在它的说明中原文是 generator based flow-control

    看过co的实现代码之后,发现其本质上也是这么实现的。只不过那个库加上了更多的边界检测代码,做的更加健壮。

    至此,就是我对于在javascript中使用yield+promise,从而对同步、异步代码进行流程控制的思考与总结。

    相关文章

      网友评论

        本文标题:Javascript 中通过 yield 和 promise 使

        本文链接:https://www.haomeiwen.com/subject/ieezzttx.html