从 生成器 到 promise+async

请叫我海龟先生 等级 1268 3 0
标签: promiseyield

本文主要讲解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 便会暂停函数执行,或许面试官想听的是 关于生成器方面的吧,如果有更好的回答,或者解释,不妨留言探讨。

收藏
评论区

相关推荐

一、手写源码之 Promise
版本一,构造函数 javascript function MyPromise(fn () {}) { // const this {} this.state 'pending' this.value undefined const resolve (value) { if (this.state
浅谈promise和js执行机制(二)
让我们继续上一次遗留的问题: setTimeout(function(){ console.log('1') }); new Promise(function(resolve){ console.log('2'); resolve(); }).then(function(){ console.log('3') }); conso
从 PHP 转到 Java
前言 最近主要编程语言从 PHP 转到了 Java。这一个多月的经历对我很有意义,所以写文章记录一下。 编程语言各有侧重,它们之间的比较没什么意义,所以本文只写一下我对两种语言的看法,以及我转到另一种语言的经历,再分享一下最近学习 Java 的心得体会。 文章欢迎转载,请尊重作者劳动成果,带上原文链接:http://www.cnblogs
为什么要从php 加入到 go 的潮流
为何我要说加入go开发是一种潮流,尤其是对于php开发人员,我加入了很多go的开发群或者爱好群,发现大部分人都是从php过来的,原本google开发golang是想让更多的c/c人员来使用。 PHP 语言作为当今最热门的网站程序开发语言,它也是我多年来一直使用的语言,它具有成本低、速度快、可移植性好、 内置丰富的函数库等优点,因此被越来越多的企业应用于网站
前端开发神器Charles从入门到卸载
前言 本文将带大家学习使用前端开发神器charles,从基本的下载安装到常见配置使用,为大家一一讲解。 一、花式夸奖Charles 截取 Http 和 Https 网络封包。 支持重发网络请求,方便后端调试。 支持修改网络请求参数。 支持网络请求的截获并动态修改。 支持模拟慢速网络。 好,骑上我心爱的小摩托,准备上路... 二、下载
Promise从入门到拿Offer之手写Promise
1、Promise构造函数的实现Promise构造函数用来声明示例对象,需要传入一个执行器函数。其中包括resolve函数和reject函数,以及几个重要的属性:状态属性、结果属性和回调函数队列。构造函数的基本框架 resolve函数用于异步处理成功后调用的函数。其中包括验证对象状态修改次数,修改promise实例对象状态,异步调用成功的回调函数
js异步的5种样式
js异步的5种样式 1.定时器 2.AJAX 3.Promise 4.Generator 5.asyns和awite  1.定时器     setTimeout() : 延时器        可以传入三个分别是            1)code:必
Python中的yield和generator
yeid可以把函数或者列表变成生成器(generator),如果只调用一部分结果,可以极大的缩减内存占用和增加运行速度,可以用next()或者循环得到生成器类型的数据。generator可以用()表示。 def fab(max): n,a,b 0,0,1 while n<max: yield b
用 async/await 来处理异步
一级标题昨天看了一篇vue的教程,作者用async/ await来发送异步请求,从服务端获取数据,代码很简洁,同时async/await 已经被标准化,是时候学习一下了。先说一下async的用法,它作为一个关键字放到函数前面,用于表示函数是一个异步函数,因为async就是异步的意思, 异步函数也就意味着该函数的执行不会阻塞后面代码的执行。 写一个async
liunx服务器web环境搭建从0到1
前几天阿里云推出了新人优惠活动,许多小伙伴都参加了。阿都整理了搭建部署环境的这篇文章帮助同学们去高效的使用服务器。文章中的搭建步骤都是阿都这几年使用并整理的。希望可以帮助到大家。  前言      本文主要讲述搭建web部署环境【nginx、mysql、java】,一般搭建环境有两种方式,一种是从官网上下载文件安装包并上传到服务器【通过xftp】进行安装,另
你要的几个JS实用工具函数(持续更新)
今天,我们来总结下我们平常使用的工具函数,希望对大家有用。1、封装fetch「源码:」/   封装fetch函数,用Promise做回调   @type get: (function()), post: (function(, ))  / const fetchUtil       get: (url)           return new 
从输入URL到页面渲染完成
从输入URL到页面渲染完成涉及网络、浏览器工作原理等知识。 前序知识 浏览器进程结构textBrowser进程 负责协调、主控,包括地址栏、书签、历史栈。GPU进程 负责整个浏览器界面的渲染网络进程 负责发起接收网络请求插件进程 控制网页中使用到的插件 如flash 渲染器进程 默认使用(Processpersiteinstance)模式 四种
从 生成器 到 promise+async
本文主要讲解js中关于生成器的相关概念和作用,以及到后面结合 promise 实现 es7中的 async 原理,你将学习到js中异步流程控制相关知识 1、认识生成器思考如下代码:javascript let x 1 function foo() x++ bar() console.log(x) // 3 function bar(
Vue 从安装到创建项目
1.安装Node可以直接在官网中下载安装默认自动安装Node和NPM(Node Package Manager) 完成后检查安装版本:node v npm v2.安装webpack webpack全局安装npm install webpack g3.安装vue脚手架 全局安装脚手架3npm install @vue/cli g 备注
盘点JavaScript中async/await知识
大家好,我是进阶学习者。一、前言Async/await 是以更舒适的方式使用 promise 的一种特殊语法,同时它也非常易于理解和使用。 二、Async function让以 async 这个关键字开始。它可以被放置在一个函数前面。如下所示:async function f() return 1;在函数前面的 “async” 这个单词表达了一个简单的