zoukankan      html  css  js  c++  java
  • 实操ES6之Promise

    箭头函数和this

    写Promise的时候,自然而然会使用箭头函数的编写方式。箭头函数就是.Neter们熟知的lambda函数,已经被大部分主流语言支持,也受到了广大码农的交口称赞,但是Jser们却会遇到不大不小的一个坑。

    众所周知,js函数中的this由调用它的上下文决定,我们还可以通过applycallbind等方式绑定上下文,从而改变函数中this的运行时指向。而当this遇到lambda时,又有所不同。

    function Test() {
        this.name = "alice"
    }
    
    Test.prototype = {
        normal: function () {
            console.info(this.name)
        },
        lambda: () => {
            console.info(this.name)
        }
    }
    
    let test = new Test()
    test.normal() //输出:alice
    test.lambda() //输出:undefined
    

    为什么会这样?网上很多分析,让人云里雾里。其实只要了解了——lambda与普通方法一个主要区别就是它能保持外层上下文变量的引用——这个特性就明白了。用lambda在方法内写过嵌套局部方法的.Neter很容易理解这个说法。

            private Action Test()
            {
                var name = "alice";            
                Action action = () => Console.WriteLine(name);
                //name将被捕获,会一直生存到action被回收
                return action;
            }
    

    so,可以将js的箭头函数理解为受运行时外层影响的内嵌函数,它是在运行时赋值的——以上述js代码为例,js解释器解释Test.prototype的定义,解释到normal函数时,是不care其内部逻辑的,继续往下解释到lambda函数时,会过一遍其内部引用到的外部变量,若有则捕获用于真正执行时(所谓词法作用域)。此时这个this指的是运行环境的根对象(在浏览器中可能就是window对象),而不是test对象(此时还不存在噻)。注:本段为个人理解。

    再看一个代码片段,请读者自行尝试分析下:

    var alice = {
        age: 18,
        getAge: function () {
            var aliceAge = this.age;//this是alice
            var getAgeWithLambda = () => this.age;//this还是alice
            var getAgeWithFunction = function () {
                return this.age;// this是window
            }
            return[aliceAge,getAgeWithLambda(),getAgeWithFunction()]
        }
    }
    
    console.info(alice.getAge()) //输出:[18, 18, undefined]
    

    Promise

    Promise主要是将原本的callback变为then,写出来的代码更便于阅读。有多种方式得到一个Promise对象:

    1. Promise.resolve()Promise.resolve('foo') 等价于 new Promise(resolve => resolve('foo'))
    2. 执行async修饰的函数:
    async function newPromise(){}
    let p = newPromise() //p就是Promise对象
    

    如果async函数中是return一个值,这个值就是Promise对象中resolve的值;如果async函数中是throw一个值,这个值就是Promise对象中reject的值。

    1. 直接构造:
    let p = new Promise((resolve, reject)=>{})
    

    注意,构造Promise时内部代码已经开始执行,只是把resolve部分挂起放到后面执行。测试代码如下:

    let p = new Promise((resolve, _) => {
        resolve(1);
        console.info(2); //率先执行
    });
    console.info(3);
    p.then(num => {
        console.info(num); //后置执行
    });
    console.info(4);
    
    //输出:2 3 4 1
    

    所以,这跟惯常认为的整个Promise代码块都后置执行不一样,需要注意。

    我们可以如上述将回调逻辑写在then里,也可以将逻辑移到外层变为同步执行(而非后置执行),这就需要用到await关键字了,它将阻塞当前代码块,等待resolve块执行完再往后执行。代码如下:

    async function test() {
        let p = new Promise((resolve, _) => {
            resolve(1);
            console.info(2);
        });
        console.info(3);
        let num = await p;
        console.info(num);
        console.info(4);
    }
    
    test()
    
    //输出:2 3 1 4
    

    ES6引入的Generator函数,是async/await的基础。

    await让我们能用同步写法写出异步方法,但事实真的如此吗?在C#领域,这么说尚且没错。后端语言大多支持多线程和线程池,await虽然阻塞了后续代码的执行,但只是上下文被挂起,线程本身是不会被阻塞的还可以干其它事情,await返回后甚至还可以让其它线程接手,可参看本人以前的博文async、await在ASP.NET[ MVC]中之线程死锁的故事。js的话,它是单线程,而且它也不像go一样有完善的协程机制,无法手动(time.sleep()、select{}等)切换代码块执行——除非等到await返回,否则线程是没机会执行其它代码块的。 错误。
    注意await挂起的不是线程,而是resolve上下文,推测本质上还是与js的执行队列相关,只不过await后续逻辑都排在resolve之后罢了。

    async function test() {
        let p = new Promise((resolve, _) => {        
            setTimeout(() => {
                resolve(1)
            }, 5000)
        });
        setTimeout(() => {
            console.info(2)
        }, 3000)
        let num = await p
        console.info(num)
    }
    
    test()
    
    //输出:2 1
    

    但使用await时仍要注意避免不必要的等待,如果前后几个Promise没有依赖关系(更精确的说法是,任务的发起条件不依赖其它任务的结果),那么最好同时发起它们,并在最后await Promise.all(promises)


    异常捕获

    很多文章都说try/catch在异步模式下无效,其实搭配await的话还是可以的(毕竟await可以使得回调执行在try块内),如下:

    let testPromise = function () {
        // throw new Error("异步异常测试")
        return Promise.reject(new Error("异步异常测试"))
    }
    
    let testInvocation = async () => {
        try {
            await testPromise()
        } catch (err) {
            console.error(`catch: ${err}`)
        }
    }
    testInvocation() //输出:catch: Error: 异步异常测试
    

    如果try的是整个testInvocation()那自然没戏。

    如果觉得在每个异步方法内部try/catch太繁琐,那么可以抽离出一个模板方法,或者使用process对象注册uncaughtExceptionunhandledRejection事件,注意这两者的区别:

    process.on('uncaughtException', e => {
        console.error(`uncaughtException: ${e.message}`)
    }); 
    process.on('unhandledRejection', (reason, promise) => {
        console.error(`unhandledRejection: ${reason}`)
    }); 
    
    let testPromise = function(){
        throw new Error("异步异常测试")
    }
    
    testPromise() //输出:uncaughtException: 异步异常测试
    
    let testInvocation = async () => await testPromise() //.catch 因为testPromise()返回的不是Promise,所以catch无效
    testInvocation() //输出:unhandledRejection: Error: 异步异常测试
    
    //注意两次异常类型不一样
    

    如果你使用electron开发桌面应用,可能无法[以process.on('unhandledRejection', ...)方式]捕获unhandledRejection异常(本人使用v10.1.0版本测试发现)。遇到这种情况,只能老老实实在每个Promise后面写catch()。

    使用process捕获异常无法获取异常的上下文,且丢失上下文堆栈使得node不能正常进行内存回收,从而导致内存泄露。
    node中还有个东西domain用于弥补process的问题,但是个人认为domain使用不便,且织入业务代码程度过深,另外据说目前版本还不稳定(后续可能会更改),甚至有文章说已被node废弃,具体什么情况暂未深入了解。总之希望node或者js平台能出一个关于异常捕获的更好的解决方案。


    协程安全

    在js场景下,异步机制更类似于Go的协程(毕竟js是单线程,多线程无从谈起),所以此处取名为协程安全。

    直接看代码:

    let policy = {}
    
    let testfun = async () => {    
        let data = await policy
        //生成随机数
        data["key"] = utility.getRandomString(20)
        return data
    }
    
    //1
    let testinfo = async () => {
        let data = await testfun()
        console.info(data.key)    
    }
    
    for (let i = 0; i < 5; i++) {
        testinfo()
    }
    
    //输出结果是5次相同的随机数
    
    //2
    let testinfo2 = async () => {
        for (let i = 0; i < 5; i++) {
            let data = await testfun()
            console.info(data.key)
        }
    }
    
    testinfo2()
    
    //如此则正常输出5次不同的随机数
    

    由上可知:在使用await时,若多个await操作相同变量,并且它们的后续操作是在所有await都返回后执行,就容易出现与预期不符的情况,应尽量避免。

  • 相关阅读:
    黄聪:获取当天最新信息的mysql语句php
    黄聪:开源PHP智能中文分词扩展:PHPCWS
    黄聪:Wordpress用get_current_screen函数来选择性加载插件中的JS和CSS
    黄聪: Javascript跨域访问解决方案
    黄聪:WordPress 角色和权限指南
    黄聪:PHP自动判断字符串是gb2312还是utf8编码
    黄聪:mysql查询今天,昨天,近7天,近30天,本月,上一月数据的方法
    黄聪:python中defaultdict标准字典的使用
    黄聪:IE6下css大bug:文字神秘消失,鼠标选择了才出现
    黄聪:C#模拟网站页面POST数据提交表单(一)WebClient (转)
  • 原文地址:https://www.cnblogs.com/newton/p/13630800.html
Copyright © 2011-2022 走看看