从 生成器 到 promise+async

请叫我海龟先生 等级 1581 3 0

本文主要讲解js中关于生成器的相关概念和作用,以及到后面结合 promise 实现 es7中的 async 原理,你将学习到js中异步流程控制相关知识

1、认识生成器

思考如下代码:

        let x = 1
        function foo() {
            x++
            bar()
            console.log(x) // 3
        }
        function bar() {
            x++
        }
        foo()

如上代码,我们确定知道,运行foo函数时,bar函数一定也会在 x++后执行,于是得到 x = 3,那么有没有其他方式,能不能在 foo函数 x++后,先暂停一下,外部在执行下 bar 函数,然后再往下执行呢?(这其实是属于抢占线程的方式,js 并不支持)如下代码:

        let x = 1
        function *foo() {
            x++
            console.log('第一次next执行到 yield处,暂停') 
            yield
            console.log('第二次执行next')
            console.log(x) // 3
        }
        function bar() {
            x++
        }
        let it = foo()
        it.next()
        bar()
        it.next()

解释:

上面代码新增了几个可能没有见过的标志符 一个*号yield*号的作用是标识foo函数是一个生成器,当生成器函数执行时,遇到内部的 yield便会暂停函数执行,而是等待下一次的 恢复执行(next)

代码释义:

// 并不会真的执行foo函数,而是构造 foo 函数的迭代器 iterator (it)
let it = foo()
// 这是第一次执行,内部会执行到 yield 便停止,等待下一次 next 再恢复执行
it.next()
在这期间便执行其他代码 bar()
// 第二次执行,从上一次 yield 处恢复执行
it.next()

上面的代码我们可以知道,生成器函数,可以实现让函数暂停和恢复执行,生成器函数先构造迭代器,然后通过迭代器上的 next 方法控制函数的执行

2、简单补充下迭代器

迭代器是一个定义良好的接口,用于从一个生产者一步步得到一系列值。(引用于 《你不知道的javascript中卷》),从上面例子中, 不断调用next方法来恢复函数执行应该能领悟到 一步步得到一系列值 这句话的意思。

迭代器会返回一个对象 {value: xxx, done: false} ,值 value 和当前迭代状态 done,会依据这个状态来判定是否结束迭代,es6 新增了 Symbol.iterator 来调用迭代,实际上部分 js 数据已经内置了 迭代方法,比如数组

var a = [1,3,5,7,9];
for (var v of a) {
 console.log( v );
}
// 1 3 5 7 9 
for of 其实就自动使用迭代来完成的,
而且在面对数组这样有限的集合时,最后会自动的 返回 done true 来停止迭代
同时我们也可以使用 break 或 return 的方式来结束迭代

3、深入一点 生成器

上面我们只是简单了解了生成器和迭代相关的知识,接下来我们稍微深入了解下生成器 思考如下代码:

        var z = 1
        function *foo() {
            var x = yield 2
            z++
            var y = yield(x * z)
            console.log(x,y,z)
        }
        var it = foo()
        var y1 =  it.next()
        var y2 =  it.next(6)
        var y3 =  it.next(8)
        有点复杂了,接下来来分析下

上面的代码比刚刚的栗子复杂多了,不仅仅是简单控制程序的启动和暂停,而是多了消息的传递(输入,输出) yield 可以向外部传递值,可以理解为return ,同时也可以接收由外部 next( xxx ) 传递过来的值。

代码释义:

        // 生成 foo 函数的迭代器
        var it = foo()
        / *第一次,执行到第一个 yield 处,
          *此时 yield 2,表示向外界输出了一个2,
          *则 y1 = {value: 2, done: false}
        */
        var y1 =  it.next()
        /**
         *第二次,从第一个 yield 处恢复执行,同时 next(6),向内部输入 6
         * x = 6 z++ ---> 2
         * 遇到 yield(x * z) 停止,并且向外返回 6*2 = 12
         * y2 = {value: 12, done: false}
        **/
        var y2 =  it.next(6)
        /**
         * 第三次,next(8) 则 内部 y = 8
        **/
        var y3 =  it.next(8)
        所以最终 console.log(x,y,z)  6 8 2

总结下:

  1. 当第一次调用生成器 var it = foo() 会构造一个迭代器,但不会真的执行
  2. 第一次调用 it.next()时, 函数会执行到第一个 yield处,并将yield 后的值返回(相当于return),(注意:第一次调用的next() 传递的参数是无效的)
  3. 第二次调用next(xx),可以传递参数,参数将赋值到 第一次 yield 处,函数继续执行到 下一个yeild处停止,(下一个yield 也可以返回值),然后一直这样进行下去
  4. it.next() 都会返回一个对象 { value: xxx,done: boole },done 用来标识迭代是否结束,当没有返回值时 value 就是 undefined

4、生成器和异步

上面我们大体了解清楚了生成器的使用,现在我们来看看用生成器来控制异步

思考如下代码:

        // 模拟一个 ajax 的请求函数
        function ajax(x,y,time = 3000) {
             setTimeout(() => {
                it.next( x + y )
             },time)
         }
         function *mian() {
             try {
                let data = yield ajax(1,2)
                console.log('ajax 结果',data)
             } catch (error) {
                 console.log('错误',error)
             }

         }
         let it = mian()
         it.next()

在之前,可能会考虑使用回调的方式去解决这样的异步问题,现在我们可以通过生成器去控制,其实就是当异步结束的时候,通过构造的迭代器去调用,从而实现异步的控制。

因为 yield阻塞 mian 函数的执行,所以当等到异步结束时,再通过 it.next( x + y )重新启动 mian 函数,从而完成异步的控制。

生成器也可以抛出错误,(it.throw)也可以通过 throw 手动输出错误,使得我们可以使用 try catch 来捕捉同步错误。

5、生成器 和 Promise

    之前写请求可能是这样的
    function foo(x,y) {
         return request(
         "http://some.url.1/?x=" + x + "&y=" + y
         );
    }
    foo( 11, 31 ).then(
         function(text){
             console.log( text );
         },
         function(err){
             console.error( err );
         }
    );

foo 会返回一个 Promise,使得我们再去操作,当搭配 生成器时

    function *main() {
         try {
              // 返回一个 Promise
             var text = yield foo( 11, 31 );
             console.log( text );
         }
         catch (err) {
             console.error( err );
         }
    } 
    var it = main()
    var p = it.next().value
    // Promise 有结果后,next 传入 结果值 
    p.then(
         function(text){
             it.next( text );
         },
         function(err){
             it.throw( err );
         }
    ); 

上面的代码可以做知道,我们可以直接 yield 一个 Promise,当Promise决议后,再输入结果值,在 es7之前,会通过一些 库取实现这样的封装,es7后新增了 async await 这样的语法

    async function main() {
             try {
                 var text = await foo( 11, 31 );
                 console.log( text );
             }
             catch (err) {
                 console.error( err );
             }
    } 

上面代码没有了 ,也不需要 yield 出 Promise,而是使用 await 等待他的决议。 *如果你 await 了一个 Promise,async 函数就会自动获知要做什么,它会暂停这个函数(就像生成器一样),直到 Promise 决议。我们并没有在这段代码中展示这一点,但是调用一个像 main() 这样的 async 函数会自动返回一个 promise。在函数完全结束之后,这个 promise会决议**。(引用于原文)

6、实现一个 async await

        // 异步请求
        function ajax(x,y,time = 3000) {
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(x + y)
                },time)
            })
         }
        // 其实结构和 async await 就很类似了
        function *mian() {
                    try {
                        let data = yield ajax(5,6)
                        return data
                    } catch (error) {
                        console.log('错误', error)
                    }
        }

        run(mian).then((res) => {
            console.log(res,'res')
        })
        /**
        * run 函数内部其实就是返回promise,循环调用生成器的方式来实现的
        **/
        function run(gen) {
            var args = [].slice.call(arguments, 1), it;
            // 在当前上下文中初始化生成器
            it = gen.apply(this, args);
            // 返回一个promise用于生成器完成
            return Promise.resolve().then(function handleNext(value) {
                // 对下一个yield出的值运行
                var next = it.next(value);
                return (function handleResult(next) {
                    // 生成器运行完毕了吗?
                    if (next.done) {
                        return next.value;
                    }
                    // 否则继续运行
                    else {
                        return Promise.resolve(next.value).then(
                            // 成功就恢复异步循环,把决议的值发回生成器
                            handleNext,
                            // 如果value是被拒绝的 promise,
                            // 就把错误传回生成器进行出错处理
                            function handleErr(err) {
                                return Promise.resolve(it.throw(err)).then(handleResult);
                            }
                        );
                    }
                })(next);
            });
        }

个人理解: async 和 await 其实就是于、把之前想要结合 生成器+promise 的方式包装了下,当async 标志的函数,内部 await 一个promise,内部就会暂停执行,当 promise有结果后(可以理解为有结果就 next( data ) 恢复函数执行),就将结果值返回了

想起以前,总有面试官问,你知道 async await 吗?我说,async 不就是一个标识符吗,表明这是一个异步函数,会自动将return 的值封装成 promise,当内部遇到 await 便会暂停函数执行,或许面试官想听的是 关于生成器方面的吧,如果有更好的回答,或者解释,不妨留言探讨。

7、后续补充

在前面我们知道了 async其实就是 Generator函数的语法糖,把 * 号换成了 async,把 yield 换成了 await,那么,这样有什么好处或者区别呢?

  1. async修饰符,使得和其他函数调用无差异,不需要调用next()方法,同时直接返回一个promise
  2. async和await语义更好,不像*和yield,同时 yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

对比下两种方式,就能体会到差异了

收藏
评论区

相关推荐

Promise从入门到拿Offer之手写Promise
1、Promise构造函数的实现Promise构造函数用来声明示例对象,需要传入一个执行器函数。其中包括resolve函数和reject函数,以及几个重要的属性:状态属性、结果属性和回调函数队列。构造函数的基本框架 resolve函数用于异步处理成功后调用的函数。其中包括验证对象状态修改次数,修改promise实例对象状态,异步调用成功的回调函数
用 async/await 来处理异步
一级标题昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await 已经被标准化,是时候学习一下了。先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async
理解 Javascript 中的 Async / Await
在本文中,我们将探讨async/await,对于每个Javascript开发人员来说,是异步编程的首选工具。如果您不熟悉javascript,请不要担心,本文将帮助您async/await从头开始理解。 介绍async/await 是javascript中的一种模式,可使您的代码以同步方式执行,但又不影响javascript的异步行为。 定义异步功能要定义一
从 生成器 到 promise+async
本文主要讲解js中关于生成器的相关概念和作用,以及到后面结合 promise 实现 es7中的 async 原理,你将学习到js中异步流程控制相关知识 1、认识生成器思考如下代码:javascript let x 1 function foo() x++ bar() console.log(x) // 3 function bar(
盘点JavaScript中async/await知识
大家好,我是进阶学习者。一、前言Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。 二、Async function让以 async 这个关键字开始。它可以被放置在一个函数前面。如下所示:async function f() return 1;在函数前面的 “async” 这个单词表达了一个简单的
java多线程(2)
java生命周期、线程通讯 ============= **一、生命周期** ----------         有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别。 ![](https://images2018.cnblogs.com/blog/1090617/201806/1090617-2018061121202
ES6——Generator
ES6 新引入了 Generator 函数,可以通过 yield 关键字,把函数的执行流挂起,为改变执行流程提供了可能,从而为异步编程提供解决方案。 Generator 函数组成 -------------- Generator 有两个区分于普通函数的部分 一是在 function 后面,函数名之前有个 \* ; 函数内部有 yield 表达式。
ES6中Generator理解
1\. 生成器函数声明    function\*  name(args){}; 2\. yield使用 function* hello(){     console.log('before hello');  //可看到hello()并不会立刻执行函数, 到第一次next调用时才会     var name =
ES6中的Promise和Generator详解
简介 == ES6中除了上篇文章讲过的语法新特性和一些新的API之外,还有两个非常重要的新特性就是Promise和Generator,今天我们将会详细讲解一下这两个新特性。 Promise ======= 什么是Promise ---------- Promise 是异步编程的一种解决方案,比传统的解决方案“回调函数和事件”更合理和更强大。 所谓P
JavaScript中Promise 使用、原理以及实现过程
#### 1.什么是 Promise promise 是目前 JS 异步编程的主流解决方案,遵循 Promises/A+ 方案。 #### 2.Promise 原理简析 (1)promise 本身相当于一个状态机,拥有三种状态 * pending * fulfilled * rejected 一个 promise 对象初始化时
Promise和Observable的映射
前言 -- 1. promise解决了嵌套地狱的问题,Observable解决了promise只有一个结果,和不可以取消的问题。 2. 使用的是rxjs6版本。 3. 这篇文章是方便使用Observable的API替换Promise的API。 ### 正常用法 promise .then(result => {}) .ca
Promise的奇怪用法和自己实现一个Promise
原文链接: [Promise的奇怪用法和自己实现一个Promise](https://my.oschina.net/ahaoboy/blog/4645165) 使用Promise实现一个页面所有图片加载完毕的回调 import React, { useEffect } from "react"; export default () =>
Promise进一步阅读
下面是几篇比较好的Promise文章: \[1\] Promise是怎么工作的, [http://wengeezhang.com/?p=13](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fwengeezhang.com%2F%3Fp%3D13) \[2\] JavaScript进阶之路
Python之yield语法
生成器与yield --------- 函数使用yield关键字可以定义生成器对象。生成器是一个函数。它生成一个值的序列,以便在迭代中使用,例如: 1 def countdown(n): 2 print('倒计时:%s' % n) 3 while n > 0: 4 yield n 5
tornado+peewee
##### 前言: * 需要异步操作MySQL,又要用orm,使用sqlalchemy需要加celery,觉得比较麻烦,选择了peewee-async ##### 开发环境 python3.6.8+peewee-async0.5.12+peewee2.10.2 * 数据库:MySQL,使用peewee-async需要依赖库 pip instal