实操ES6之Promise
箭头函数和this
写Promise的时候,自然而然会使用箭头函数的编写方式。箭头函数就是.Neter们熟知的lambda函数,已经被大部分主流语言支持,也受到了广大码农的交口称赞,但是Jser们却会遇到不大不小的一个坑。
众所周知,js函数中的this由调用它的上下文决定,我们还可以通过apply、call、bind等方式绑定上下文,从而改变函数中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对象:
Promise.resolve():Promise.resolve('foo')等价于new Promise(resolve => resolve('foo'))- 执行
async修饰的函数:
async function newPromise(){}
let p = newPromise() //p就是Promise对象
如果async函数中是return一个值,这个值就是Promise对象中resolve的值;如果async函数中是throw一个值,这个值就是Promise对象中reject的值。
- 直接构造:
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对象注册uncaughtException和unhandledRejection事件,注意这两者的区别:
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都返回后执行,就容易出现与预期不符的情况,应尽量避免。
实操ES6之Promise的更多相关文章
- ABP入门系列(1)——学习Abp框架之实操演练
作为.Net工地搬砖长工一名,一直致力于挖坑(Bug)填坑(Debug),但技术却不见长进.也曾热情于新技术的学习,憧憬过成为技术大拿.从前端到后端,从bootstrap到javascript,从py ...
- ES6的promise对象研究
ES6的promise对象研究 什么叫promise? Promise对象可以理解为一次执行的异步操作,使用promise对象之后可以使用一种链式调用的方式来组织代码:让代码更加的直观. 那我们为什么 ...
- 号外号外:9月13号《Speed-BI云平台案例实操--十分钟做报表》开讲了
引言:如何快速分析纷繁复杂的数据?如何快速做出老板满意的报表?如何快速将Speed-BI云平台运用到实际场景中? 本课程将通过各行各业案例背景,将Speed-BI云平台运用到实际场景中 ...
- Mysql MHA(GTID)配置(实操)
实现环境 centos6.7 MYSQL5.6.36 主:192.168.1.191 从1:192.168.1.145 从2:192.168.1.146 监测:放在从2上 192.168.1.146 ...
- Selenium之unittest测试框架详谈及实操
申明:本文是基于python3.x及selenium3.x. unittest,也可以称为PyUnit,可以用来创建全面的测试套件,可以用于单元自动化测试(模块).功能自动化测试(UI)等等. 官方文 ...
- unittest测试框架详谈及实操(二)
类级别的setUp()方法与tearDown()方法 在实操(一)的例子中,通过setUp()方法为每个测试方法都创建了一个Chrome实例,并且在每个测试方法执行结束后要关闭实例.是不是觉得有个多余 ...
- .net基础学java系列(四)Console实操
上一篇文章 .net基础学java系列(三)徘徊反思 本章节没啥营养,请绕路! 看视频,不实操,对于上了年龄的人来说,是记不住的!我已经看了几遍IDEA的教学视频: https://edu.51cto ...
- RTN 实操
创建房间 test-rtn 10001 e2uii6r7r 8LfwOcreM76OiV1V1y8jXrMG_BNa-cmktpWUznRa:kdYdsEpcYLc5ceWEHPaK0ZDI7Qc=: ...
- 6.3 Pandora 实操 - 数据立方
简介 数据立方是适用于大规模实时数据(每天百亿条,10TB+ 级别数据)查询与分析的数据库系统,提供交互式的访问数据的能力,支持数据过滤.分组.聚合,实现亚秒级以内对亿行级别的数据表进行多维探索分析. ...
随机推荐
- 005_针对于go语言中速率限制的思考
在之前的go语言的速率限制这篇文章里,我们尝试了普通的速率限制,和脉冲型速率限制.其中,脉冲型速率限制是放开了限制,里面有3个请求是一次性到达,然后再按照200ms的速度限制的,之前的代码如下所示: ...
- Python 超简单 提取音乐高潮(附批量提取)
很多时候我们想提取某首歌的副歌部分(俗称 高潮部分),只能手动直接卡点剪切,但是对于大批量的获取就很头疼,如何解决? 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后 ...
- Nginx - location常见配置指令,alias、root、proxy_pass
1.[alias]——别名配置,用于访问文件系统,在匹配到location配置的URL路径后,指向[alias]配置的路径.如: location /test/ { alias/first/secon ...
- 【av68676164(p15-p17)】进程概念
from av68676164 4.1.1 进程的基本概念 程序运行在并发环境中的问题 运行过程不确定 结果不可再现(程序运行被干扰) 解决方案:对运行过程施加约束 新的概念:进程 描述和管理程序的& ...
- Java 命令行 编译、执行、打包
Java 命令行 编译.执行.打包 一般来说 IDE 能够很方便的编译打包. 我写这篇文章是遇到了不能使用 IDE 的情况,简单记录一下,不做深入探讨. 环境 linux jdk 1.8 简单的编译执 ...
- [BZOJ4237]稻草人 题解
我们考虑分治一下 按\(x\)坐标排序 然后对于每一段的两部分都按\(y\)排序 左右两边都维护一个单调栈 然后考虑右边对左边的贡献就行了 #include <bits/stdc++.h> ...
- C#LeetCode刷题之#671-二叉树中第二小的节点(Second Minimum Node In a Binary Tree)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/4100 访问. 给定一个非空特殊的二叉树,每个节点都是正数,并且每 ...
- GitHub标星120K+的JDK并发编程指南,连续霸榜GitHub终于开源了
前言 在编程行业中,有一个东西是和广大程序员形影不离的,在最一开始接触编程就是配置它的运行环境,然后java / javac,对,这个东西就是jdk 昨天项目刚上线,可以稍微休息一下了,但是猛的闲下来 ...
- 【AI 算法评测】BERT 对 NLP 效果的改善,不负众望!
AI 在各大领域的发展有目共睹,而作为人工智能皇冠上的明珠--自然语言处理却成果了了,大多实现或者以半成品的形式躺在实验室中,或者仅仅作为某个产品的辅助功能.而这一情况在 BERT 出现后出现了很大的 ...
- JavaScript设计模式之命令模式【命令解耦】
在讲解命令模式之前我们先来了解一个生活中的命令模式场景: 场景1: 医院看病抓药: 当你因为肾虚到医院看医生,医生一番操作之后得出结论:要吃个疗程[夏桑菊].[小柴胡](药名纯属虚构,真的肾虚就找医生 ...