Promise杂记
- 前言
- API
- Promise特点
- 状态跟随
- V8中的async await和Promise
- 实现一个Promise
- 参考
前言
作为一个前端开发,使用了Promise一年多了,一直以来都停留在API的调用阶段,没有很好的去深入。刚好最近阅读了V8团队的一篇如何实现更快的async await,借着这个机会整理了Promise的相关理解。文中如有错误,请轻喷~
API
Promise是社区中对于异步的一种解决方案,相对于回调函数和事件机制更直观和容易理解。ES6 将其写进了语言标准,统一了用法,提供了原生的Promise对象。
这里只对API的一些特点做记录,如果需要详细教程,推荐阮老师的Promise对象一文
new Promise
--创建一个promise实例
Promise.prototype.then(resolve, reject)
--then方法返回一个新的Promise实例
Promise.prototype.catch(error)
--.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
--错误会一直传递,直到被catch,如果没有catch,则没有任何反应
--catch返回一个新的Promise实例
Promise.prototype.finally()
--指定不管 Promise 对象最后状态如何,都会执行的操作。
--实现如下:
Promise.prototype.finally = function (callback) {
let P = this.constructor;
return this.then(
value => P.resolve(callback()).then(() => value),
reason => P.resolve(callback()).then(() => { throw reason })
);
};
Promise.all([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例
--所有子promise执行完成后,才执行all的resolve,参数为所有子promise返回的数组
--某个子promise出错时,执行all的reject,参数为第一个被reject的实例的返回值
--某个子promise自己catch时,不会传递reject给all,因为catch重新返回一个promise实例
Promise.race([promise Array])
--将多个 Promise 实例,包装成一个新的 Promise 实例。
--子promise有一个实例率先改变状态,race的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给race的回调函数。
Promise.resolve()
--将现有对象转为 Promise 对象
--参数是promise实例, 原封不动的返回
--参数是一个thenable对象 将这个对象转为 Promise 对象,状态为resolved
--参数是一个原始值 返回一个新的 Promise 对象,状态为resolved
--不带有任何参数 返回一个resolved状态的 Promise 对象。
--等价于如下代码
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
Promise.reject()
--返回一个新的 Promise 实例,该实例的状态为rejected
--Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。
Promise特点
很多文章都是把resolve当成fulfilled,本文也是,但本文还有另外一个resolved,指的是该Promise已经被处理,注意两者的区别
1. 对象具有三个状态,分别是pending(进行中)、fulfilled(resolve)(已成功)、reject(已失败),并且对象的状态不受外界改变,只能从pending到fulfilled或者pending到reject。
2. 一旦状态被改变,就不会再变,任何时候都能得到这个结果,与事件回调不同,事件回调在事件过去后无法再调用函数。
3. 一个promise一旦resolved,再次resolve/reject将失效。即只能resolved一次。
4. 值穿透,传给then或者catch的参数为非函数时,会发生穿透(下面有示例代码)
5. 无法取消,Promise一旦运行,无法取消。
6. 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
7. 处于pending时,无法感知promise的状态(刚刚开始还是即将完成)。
值穿透代码:
new Promise(resolve=>resolve(8))
.then()
.then()
.then(function foo(value) {
console.log(value) // 8
})
状态追随
状态追随的概念和下面的v8处理asyac await相关联
状态跟随就是指将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,即B的状态会追随A。
如下代码所示:
const promiseA = new Promise((resolve) => {
setTimeout(() => {
resolve('ccc')
}, 3000)
})
const promiseB = new Promise(res => {
res(promiseA)
})
promiseB.then((arg) => {
console.log(arg) // print 'ccc' after 3000ms
})
按理说,promiseB应该是已经处于resolve的状态, 但是依然要3000ms后才打印出我们想要的值, 这难免让人困惑
在ES的标准文档中有这么一句话可以帮助我们理解:
A resolved promise may be pending, fulfilled or rejected.
就是说一个已经处理的promise,他的状态有可能是pending, fulfilled 或者 rejected。 这与我们前面学的不一样啊, resolved了的promise不应该是处于结果状态吗?这确实有点反直觉,结合上面的例子看,当处于状态跟随时,即使promiseB立即被resolved了,但是因为他追随了promiseA的状态,而A的状态则是pending,所以才说处于resolved的promiseB的状态是pending。
再看另外一个例子:
const promiseA = new Promise((resolve) => {
setTimeout(() => {
resolve('ccc')
}, 3000)
})
const promiseB = new Promise(res => {
setTimeout(() => {
res(promiseA)
}, 5000)
})
promiseB.then((arg) => {
console.log(arg) // print 'ccc' after 5000ms
})
其实理解了上面的话,这一段的代码也比较容易理解,只是因为自己之前进了牛角尖,所以特意记录下来:
- 3s后 promiseA状态变成resolve
- 5s后 promiseB被resolved, 追随promiseA的状态
- 因为promiseA的状态为resolve, 所以打印 ccc
V8中的async await和Promise
在进入正题之前,我们可以先看下面这段代码:
const p = Promise.resolve();
(async () => {
await p;
console.log("after:await");
})();
p.then(() => {
console.log("tick:a");
}).then(() => {
console.log("tick:b");
});
V8团队的博客中, 说到这段代码的运行结果有两种:
Node8(错误的):
tick a
tick b
after: await
Node10(正确的):
after await
tick a
tick b
ok, 问题来了, 为啥是这个样子?
先从V8对于await的处理说起, 这里引用一张官方博客的图来说明Node8 await的伪代码:
Node8 await
对于上面的例子代码翻译过来就(该代码引用自V8是怎么实现更快的async await)是:
const p = Promise.resolve();
(() => {
const implicit_promise = new Promise(resolve => {
const promise = new Promise(res => res(p));
promise.then(() => {
console.log("after:await");
resolve();
});
});
return implicit_promise;
})();
p.then(() => {
console.log("tick:a");
}).then(() => {
console.log("tick:b");
});
很明显,内部那一句 new Promise(res => res(p)); 代码就是一个状态跟随,即promise追随p的状态,那这跟上面的结果又有什么关系?
在继续深入之前, 我们还需要了解一些概念:
task和microtask
JavaScript 中有 task 和 microtask 的概念。 Task 处理 I/O 和计时器等事件,一次执行一个。 Microtask 为 async/await 和 promise 实现延迟执行,并在每个任务结束时执行。 总是等到 microtasks 队列被清空,事件循环执行才会返回。
如官方提供的一张图:
EnqueueJob、PromiseResolveThenableJob和PromiseReactionJob
EnquequeJob: 存放两种类型的任务, 即PromiseResolveThenableJob和PromiseReactionJob, 并且都是属于microtask类型的任务
PromiseReactionJob: 可以通俗的理解为promise中的回调函数
PromiseResolveThenableJob(promiseToResolve, thenable, then): 创建和 promiseToResolve 关联的 resolve function 和 reject function。以 then 为调用函数,thenable 为this,resolve function和reject function 为参数调用返回。(下面利用代码讲解)
状态跟随的内部
再以之前的代码为例子
const promiseA = new Promise((resolve) => {
resolve('ccc')
})
const promiseB = new Promise(res => {
res(promiseA)
})
当promiseB被resolved的时候, 也就是将一个promise(代指A)当成另外一个promise(代指B)的resolve参数,会向EnquequeJob插入一个PromiseResolveThenableJob任务,PromiseResolveThenableJob大概做了如下的事情:
() => {
promiseA.then(
resolvePromiseB,
rejectPromiseB
);
}
并且当resolvePromiseB执行后, promiseB的状态才变成resolve,也就是B追随A的状态
Node 8中的流程
1. p处于resolve状态,promise调用then被resolved,同时向microtask插入任务PromiseResolveThenableJob
2. p.then被调用, 向microtask插入任务tickA
3. 执行PromiseResolveThenableJob, 向microtask插入任务resolvePromise(如上面的promiseA.then(...))
4. 执行tickA(即输出tick: a),返回一个promise, 向microtask插入任务tickB
5. 因为microtask的任务还没执行完, 继续
6. 执行resolvePromise, 此时promise终于变成resolve, 向microtask插入任务'after await'
7. 执行tickB(即输出tick: b)
8. 执行'after await'(即输出'after await')
Node 10 await
老规矩, 先补一张伪代码图:
翻译过来就是酱紫:
const p = Promise.resolve();
(() => {
const implicit_promise = new Promise(resolve => {
const promise = Promise.resolve(p)
promise.then(() => {
console.log("after:await");
resolve();
});
});
return implicit_promise;
})();
p.then(() => {
console.log("tick:a");
}).then(() => {
console.log("tick:b");
});
因为p是一个promise, 然后Promise.resolve会直接将P返回,也就是
p === promise // true
因为直接返回了p,所以省去了中间两个microtask任务,并且输出的顺序也变得正常,也就是V8所说的更快的async await
实现一个Promise
先实现一个基础的函数
function Promise(cb) {
const that = this
that.value = undefined // Promise的值
that.status = 'pending' // Promise的状态
that.resolveArray = [] // resolve函数集合
that.rejectArray = [] // reject函数集合
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function() {
if (that.status === 'pending') { // 处于pending状态 循环调用
that.value = value
that.status = 'resolve'
for(let i = 0; i < that.resolveArray.length; i++) {
that.resolveArray[i](value)
}
}
})
}
function reject(reason) {
if (reason instanceof Promise) {
return reason.then(resolve, reject)
}
setTimeout(function() {
if (that.status === 'pending') { // 处于pending状态 循环调用
that.value = reason
that.status = 'reject'
for(let i = 0; i < that.rejectArray.length; i++) {
that.rejectArray[i](reason)
}
}
})
}
try {
cb(resolve, reject)
} catch (e) {
reject(e)
}
}
Promise.prototype.then = function(onResolve, onReject) {
var that = this
var promise2 // 返回的Promise
onResolve = typeof onResolve === 'function' ? onResolve : function(v) { return v } //如果不是函数 则处理穿透值
onReject = typeof onReject === 'function' ? onReject : function(v) { return v } //如果不是函数 则处理穿透值
if (that.status === 'resolve') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
const x = onResolve(that.value)
if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
x.then(resolve, reject)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
})
})
}
if (that.status === 'reject') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() {
try {
const x = onResolve(that.value)
if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
x.then(resolve, reject)
} else {
reject(x)
}
} catch (e) {
reject(e)
}
})
})
}
if (that.status === 'pending') {
return promise2 = new Promise(function(resolve, reject) {
that.resolveArray.push(function(value) {
try {
var x = onResolve(value)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
that.rejectArray.push(function(reason) {
try {
var x = onReject(reason)
if (x instanceof Promise) {
x.then(resolve, reject)
}
} catch (e) {
reject(e)
}
})
})
}
}
Promise.prototype.catch = function(onReject) {
return this.then(null, onReject)
}
参考
v8是怎么实现更快的 await ?深入理解 await 的运行机制
V8中更快的异步函数和promise
剖析Promise内部结构,一步一步实现一个完整的、能通过所有Test case的Promise类
PromiseA+
ES6入门
深入Promise
Promise杂记的更多相关文章
- Webpack系列-第三篇流程杂记
系列文章 Webpack系列-第一篇基础杂记 Webpack系列-第二篇插件机制杂记 Webpack系列-第三篇流程杂记 前言 本文章个人理解, 只是为了理清webpack流程, 没有关注内部过多细节 ...
- webpack-插件机制杂记
系列文章 Webpack系列-第一篇基础杂记 webpack系列-插件机制杂记 前言 webpack本身并不难,他所完成的各种复杂炫酷的功能都依赖于他的插件机制.或许我们在日常的开发需求中并不需要自己 ...
- es6杂记
es6杂记 let 和 const let 仅在代码块里有效 { let a = 10; var b = 1; } a // ReferenceError: a is not defined. b / ...
- Javascript - Promise学习笔记
最近工作轻松了点,想起了以前总是看到的一个单词promise,于是耐心下来学习了一下. 一:Promise是什么?为什么会有这个东西? 首先说明,Promise是为了解决javascript异步编 ...
- 路由的Resolve机制(需要了解promise)
angular的resovle机制,实际上是应用了promise,在进入特定的路由之前给我们一个做预处理的机会 1.在进入这个路由之前先懒加载对应的 .js $stateProvider .state ...
- angular2系列教程(七)Injectable、Promise、Interface、使用服务
今天我们要讲的ng2的service这个概念,和ng1一样,service通常用于发送http请求,但其实你可以在里面封装任何你想封装的方法,有时候控制器之间的通讯也是依靠service来完成的,让我 ...
- 闲话Promise机制
Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval.DOM事件机制.ajax,通过传入回调函数实现控制反转.异步编程为js ...
- 深入理解jQuery、Angular、node中的Promise
最初遇到Promise是在jQuery中,在jQuery1.5版本中引入了Deferred Object,这个异步队列模块用于实现异步任务和回调函数的解耦.为ajax模块.队列模块.ready事件提供 ...
- Promise的前世今生和妙用技巧
浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一 ...
随机推荐
- 如何把if-else代码重构成高质量代码
原文:https://blog.csdn.net/qq_35440678/article/details/77939999 本文提纲: 为什么我们写的代码都是if-else? 这样的代码有什么缺点? ...
- Vue(二十八)el-cascader 动态加载 - 省市区组件
1.后台接口为点击加载下一级 ,传省市区id <template> <el-cascader v-model="selectedOptions" placehol ...
- 查找datatable 中的重复记录(只查询一个字段)
StringBuilder str = new StringBuilder(); var res = new ResParameter() { code = ResponseCode.exceptio ...
- 什么是 ajax?----异步 javascript 和 xml
GET 用于请求服务器数据 POST 用于上传数据到服务器,或者修改服务器数据 ajax 异步通信,实现页面的局部刷新,按需获取数据,节约带宽,带来更好的用户体验 客户端与服务器在不必刷新浏览器的情况 ...
- 航班座位_hihocoder
题目2 : 航班座位 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi在给一个家庭旅游团订某次航班的机票.该航班的飞机一共有N排座位,每排座位有10个座位,从左到右 ...
- Reactjs组件中的方法为什么绑定this?
前言:Reactjs中的定义的方法其实是基于实例的原型方法:es6默认严格模式 问题:在Reactjs组件中写入以下代码,那么执行结果会是什么呢? ··· 结果是这样的: 对的,你没看错,不是do ...
- js-day03-事件响应和练习题
DOM事件编程 事件驱动编程:所谓事件驱动,简单地说就是你点什么按钮(即产生什么事件),电脑执行什么操作(即调用什么函数).当然事件不仅限于用户的操作. 当对象处于某种状态时,可以发出一个消息通知,然 ...
- redhat 6 红帽6 Linux 网络配置
[root@ORAMYSQL ~]# cd /etc/sysconfig/network-scripts/ 切换到网卡配置目录 [root@ORAMYSQL network-scripts]# vi ...
- [Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II
Given an array A of non-negative integers, half of the integers in A are odd, and half of the intege ...
- linux入门--Linux系统的优缺点
1) 大量的可用软件及免费软件 Linux 系统上有着大量的可用软件,且绝大多数是免费的,比如声名赫赫的 Apache.Samba.PHP.MySQL 等,构建成本低廉,是 Linux 被众多企业青睐 ...