实操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都返回后执行,就容易出现与预期不符的情况,应尽量避免。

(0)

相关推荐

  • 【5min+】帮我排个队,谢谢。await Task.Yield()

    系列介绍 [五分钟的dotnet]是一个利用您的碎片化时间来学习和丰富.net知识的博文系列.它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net ...

  • C# 异步编程

    基于Task的异步编程模式(TAP)是Microsoft为.Net平台下使用Task进行编程所提供的一组建议,这种模式提供了可以被await消耗(调用)方法的APIs,并且当使用async关键字编写遵 ...

  • 异步解决方案----Promise与Await

    目录 前言 一.Promise的原理与基本语法 1.Promise的原理 2.Promise的基本语法 二.Promise多个串联操作 三.Promise常用方法 四.Async/Await简介与用法 ...

  • promise的常用情况

    因为js是单线程的,所以一旦代码中有报错,就不会执行下面的了,如下333就未打印 console.log(111) throw Error(222) console.log(333) 好像与promi ...

  • 如何在现代JavaScript中编写异步任务

    前言在本文中,我们将探讨过去异步执行的 JavaScript 的演变,以及它是怎样改变我们编写代码的方式的.我们将从最早的 Web 开发开始,一直到现代异步模式.作为编程语言, JavaScript ...

  • 深浅克隆和Promise异步编程

    深克隆和浅克隆 浅克隆 arr.slice(0) arr.concat() let obj2 = {... obj} 深克隆 function deepClone(obj){ //判断参数是不是一个对 ...

  • 一个学习 Koa 源码的例子

    作者: MarkLin 学习目标: 原生 node 封装 中间件 路由 Koa 原理 一个 nodejs 的入门级 http 服务代码如下, // index.js const http = requ ...

  • 徐州工程二级造价实操培训班推荐哪家好

    熟悉招标文件的图纸 这个时候要注意的就是有没有比较少见的材料及工艺,如果有,应该尽快准备好其材料价格或分包价格. 2搜集材料价格主要是大宗材料.如商品砼及钢筋.水泥的价格(注意考虑税的因素),砖块的价 ...

  • 研发人员薪酬设计实操指南

    Part 1 研发人员管理场景 如果你仔细观察公司研发人员,你有可能会发现下列现象: 1.研发人员只对承担的项目负责,对其他人的事情毫不关心: 2.研发部门凭印象和感觉增加工资: 3.公司想重点培育的 ...

  • 冷阳关东冷四针实操演示

    冷阳关东冷四针实操演示

  • 网购兰花买到激素苗怎么养护?做到这4点实操技巧,激素苗也不怕

    今天有网友问到,网购兰花的根呈黄黑色,根尖无水晶头又发黑是怎么回事?是否为肥害,有什么后期补救措施? 敲重点:网购的兰花根系呈黄黑色,根尖无水晶头且发黑,这不是肥害,不是肥害,不是肥害,而是典型的激素 ...

  • 财务高手怎样判断数据是否异常?(实操版)

    版权声明 本文来源于:审计之家(ID:sj20130516)微信公众号 导言 财务部究竟如何判断自己每天要看的数据是否异常? 有一天,我正在极其认真地研究一份财务报告,忽然听到背后老大幽幽的一句:看这 ...

  • 「食环药辩护」邓楚开、谢蓓:污染环境类案件辩护实操

    污染环境类案件辩护实操--以某被控污染环境案辩护经验为范例的分析 本文通过分享自己办理个案的经历,以解剖一只麻雀的方式,具体介绍拿到一个污染环境刑事案件后,怎么进行辩护. 本案是发生在浙江省某县的一起 ...

  • 债权融资计划业务实操(上)

    之前在<聊一聊近期热门的债权融资计划>里,介绍过债权融资计划的基础内容. 稍微复习总结一下. 债权融资计划的融资人发行条件.对融资人的发行要求很宽松,只要最近12个月内不存在违法违规行为即 ...

  • 债权融资计划业务实操(下)

    上一篇文章开头就给大家复习总结了北金所债权融资计划的5大优势. 聪明(ji zei)的小伙伴,已经放进口袋,带着出去营销客户.承揽业务了.虽然没有面面俱到,临时抱佛脚还是管用. 提到这事,是想告诉大家 ...

  • 股权激励方案设计与税务规划落地实操班

    企业之间的竞争是激励机制的竞争,股权激励是企业发展绕不开的话题,面对股权激励,你是不是正面临如下困惑: 股权激励要不要做?股权激励什么时候做?股权激励怎么做?股权激励怎么管? 股权分给哪些人,什么时候 ...