Promise源码分析
Promise的底层是浏览器实现的,所以我只是实现了里的功能,并非和源码一摸一样。下面为部分源码,完整源码移驾到github中下载:
https://github.com/young-monk/my-promise.git
我们首先来定义一个MyPromise类,我在这里使用立即执行函数的方式,防止变量全局污染。
const MyPromise = (() => {
//在这里定义内部变量
return class {
//构造器
constructor(executor) {
//...
}
})()
接下来,定义一些内部使用的变量:我这里使用了Symbol
,使用Symbol
的原因是改变量只能内部使用
const MyPromise = (() => {
//在这里定义内部变量
const PENDING = "pending"
const RESOLVED = "resolved"
const REJECTED = "rejected"
//使用Symbol是为了仅供内部使用
const PromiseStatus = Symbol("PromiseStatus")//当前状态
const PromiseValue = Symbol("PromiseValue")//当前值
return class {
//构造器
constructor(executor) {
//...
}
})()
我们都知道,当我们new Promise
的时候,会有一个状态,会有值,还会传入两个函数,resolve
和reject
。那么我们实现一下:
constructor(executor) {
//初始化
this[PromiseStatus] = PENDING //当前状态
this[PromiseValue] = undefined //当前值
const resolve = (data) => {
//...
}
const reject = (data) => {
//...
}
//执行
executor(resolve, reject)
}
接下来,我们就要实现resolve
和reject
函数的功能,其实这两个函数的功能非常简单,就是修改当前的状态和值,并且如果有任务队列,我们就执行任务队列中的内容。我把这两个函数的功能封装一下:
[changeStatus](data, status, queue) {
//如果已经是已决状态,那么直接结束
if (this[PromiseStatus] !== PENDING) return;
this[PromiseStatus] = status //修改当前状态
this[PromiseValue] = data //修改值
//变成已决阶段后,执行相应的队列函数
queue.forEach(q => q(data))
}
//---- 在 resolve 函数中调用
this[changeStatus](data, RESOLVED, this[thenables])
//---- 在 reject 函数中调用
this[changeStatus](data, REJECTED, this[catchables])
接下来我们要实现then
和catch
这两个后续处理函数,这两个函数实现什么功能想必大家都知道了,但我们要注意的是,如果调用then或者catch的时候,当前的状态已经是已决状态,那么就要直接执行。如果不是,那么就加入任务队列中,我在构造器中已经声明了 thenables
和 catchables
变量(then
和catch
函数的任务队列)
//settled then处理函数
then(thenable, catchable) {
//每个then都要返回一个新的promise
return this[linkPromise](thenable, catchable)
}
//settled catch处理函数
catch (catchable) {
return this[linkPromise](undefined, catchable)
}
因为功能相同,我进行了 2 次封装,都是哪两次封装呢?这是第一次,也就是实现加入任务队列的功能:
[settledHandler](handler, status, queue) {
//如果不是函数,那么直接返回
if (typeof handler !== "function") return
if (this[PromiseStatus] === status) {
//如果已经是已决状态,直接执行
setTimeout(() => {
handler(this[PromiseValue])
}, 0);
} else {
//处于未决状态,加入任务队列
queue.push(handler)
}
}
还有一次封装,也就是我们上面调用的 linkPromise
函数,为什么要调用这个函数?因为then
和catch
后续处理函数,都要返回一个新的Promise
,我们什么时候知道,then 或 catch 函数执行了,该返回新的Promise呢?这里是比较难的一部分,我们可以将 settledHandler
也就是执行处理函数的功能,反正创建一个新的 Promise中执行:
[linkPromise](thenable, catchable) {
function exec(data, handler, resolve, reject) {
try {
//获取返回值
const res = handler(data)
//如果返回的是一个Promise,此时我们直接处理一下就可以
if (res instanceof MyPromise) {
res.then(data => resolve(data), err => reject(err))
} else {
//改变状态,和修改值
resolve(res)
}
} catch (error) {
reject(error)
}
}
//返回新的Promise
return new MyPromise((resolve, reject) => {
//处理then的
this[settledHandler](data => {
//如果传过来的thenable不是函数,那么直接resolve下并结束
if (typeof thenable !== "function") {
resolve(data)
return
}
//我们把操作相同的提取封装一下
exec(data, thenable, resolve, reject)
}, RESOLVED, this[thenables])
//处理catch的
this[settledHandler](data => {
//如果传过来的thenable不是函数,那么直接reject下并结束
if (typeof catchable !== "function") {
reject(data)
return
}
//我们把操作相同的提取封装一下
exec(data, catchable, resolve, reject)
}, REJECTED, this[catchables])
})
}
如果你能看到这里,说明你已经对Promise已经完全掌握了,下面实现的是几个Promise的静态成员:
all成员
static all(proms) {
return new MyPromise((resolve, reject) => {
const results = proms.map(p => {
var obj = {
result: undefined,
isResolved: false
}
//判断是否已经全部完成
p.then(data => {
obj.result = data
obj.isResolved = true
//得到是否有非resolve状态的
const unResolved = results.filter(res => !res.isResolved)
if (unResolved.length === 0) {//如果没有 那么我们返回新的Promise
resolve(results.map(res => res.result))
}
//如果有一个reject状态,那么直接返回
}, err => reject(err))
return obj
})
console.log(results)
})
}
race成员
static race(proms) {
return new MyPromise((resolve, reject) => {
proms.forEach(p => {
p.then(data => resolve(data), err => reject(err))
})
})
}
resolve和reject静态成员
static resolve(data) {
//如果穿过来的是一个Promise,直接返回就可以
if (data instanceof MyPromise) {
return data
}
return new MyPromise(resolve => resolve(data))
}
static reject(err) {
return new MyPromise((resolve, reject) => {
reject(err)
})
}
Promise正确打开方式
1、异步处理的通用模型
ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettled
和 settled
-
unsettled:未决阶段,表示事情还在进行前期的处理,并没有发生通向结果的那件事
-
settled:已决阶段,事情已经有了一个结果,不管这个结果是好是坏,整件事情无法逆转
异步操作总是从 未决阶段 逐步发展到 已决阶段的。并且,未决阶段拥有控制何时通向已决阶段的能力。
异步操作分成了三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)
- pending:进行中,处于未决阶段,则表示这件事情还在进行(最终的结果还没出来)
- fulfilled:已成功,已决阶段的一种状态,表示整件事情已经出现结果,并是一个可以按照正常逻辑进行下去的结果
- rejected:已失败,已决阶段的一种状态,表示整件事情已经出现结果,并是一个无法按照正常逻辑进行下去的结果,通常用于表示有一个错误
一旦这种状态改变,就会固定,不会再改变。是不可逆的Promise状态的改变只有两种情况:
- 1、从
pending
通过resolve()
变为fulfilled
。 - 2、从
pending
通过reject()
变为rejected
。
参考流程图:
2、Promise及其基本使用
为了解决地狱回调和异步操作之间的联系,ES6提出了一种异步编程大的解决方案Promise。但是Promise并没有消除回调,只是让回调变得可控。
Promise是一个对象,它可以获取异步操作的消息。为了方便和简易,下面的resolved
统指fulfilled
状态
const pro = new Promise((resolve, reject)=>{
if(true){
resolve()
}else{
reject()
}
})
pro.then(data=>{
},err=>{
})
pro.catch(err=>{
})
注意:
-
Promise创建后会立即执行。
-
thenable
和catchable
函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,加入的队列是微队列。 -
在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向
rejected
,并会被catchable
捕获。 -
一旦状态推向了已决阶段,无法再对状态做任何更改。
-
Promise并没有消除回调,只是让回调变得可控。
const pro = new Promise((resolve,reject)=>{
console.log("a")
setTimeout(() => {
console.log("d")
}, 0);
resolve(1)
console.log("b")
})
pro.then(data=>{
console.log(data)
})
console.log("c")
//a b c 1 d
以上代码,立即创建一个Promise函数,并且立即执行函数中的代码,所以首先输出a
,随后将setTimeout
中代码加入宏队列,然后通过resolve()
将Promise
的状态推向已决状态,但是resolve也是异步的,他会加入到微队列中等同步代码执行完毕后再执行。随后输出b
,Promise
函数执行完后,又执行剩下的同步代码输出c
,同步代码执行完毕后,执行微队列中的输出resolve
的结果值1
,再执行宏队列中的setTimeout
,输出b
。
3、Promise的方法then,catch,finally
(1)then()和catch()
then():注册一个后续处理函数,当Promise为resolved
状态时运行该函数
catch():注册一个后续处理函数,当Promise为rejected
状态时运行该函数
Promise对象中,无论是then
方法还是catch
方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:
- 如果当前的Promise是未决的,得到的新的Promise是进行中状态
- 如果当前的Promise是已决的,会运行响应的后续处理函数,并将后续处理函数的结果(返回值)作为
resolved
状态数据,应用到新的Promise中;如果后续处理函数发生错误,则把返回值作为rejected状态数据,应用到新的Promise中。
注意:后续的Promise一定会等到前面的Promise有了后续处理结果后,才会变成已决状态
如果前面的Promise的后续处理,返回的是一个Promise,则返回的新的Promise状态和后续处理返回的Promise状态保持一致。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
console.log(pro1)
// 异步调用,
const pro2 = pro1.then(result => result *2)
console.log(pro2)//pro2是一个Promise对象,状态是pending
pro2.then(result=>{console.log(result)},err=>console.log(err))
上述代码,在执行console.log(pro2)
的时候是同步执行, 此时pro1.then()
还未执行完毕,所以promise
还是pending
状态
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
//6
上述代码,对于串联的Promise,then和catch均返回一个全新的Promise,所以在pro1的catch执行时返回的pro2执行的是正常的,并非抛出错误。所以执行的为err * 3
,result * 2
。
const pro1 = new Promise((resolve,reject)=>{
throw 1
})
const pro2 = pro1.then(result => result *2,err=> err*3)
pro1.catch(err=>err*2)
pro2.then(result=>{console.log(result*2)},err=>console.log(err*3))
上述代码,每一次返回都是一个全新的Promise,所以pro1.catch(err=>err*2)
并没有变量接收。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
resolve(2)
})
const pro3 = pro1.then(result=>{
console.log(`结果${result}`) //1
return pro2
})
//pro3的状态是pending
pro3.then(result=>{
console.log(result)//2
})
上述代码,第一个then
方法指定的回调函数,返回的是另一个Promise
对象。这时,第二个then
方法指定的回调函数,就会等待这个新的Promise
对象状态发生变化后再执行
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
const pro2 = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve(2)
}, 3000);
})
pro1.then(result=>{
console.log(`结果${result}`) //1
return pro2
}).then(result=>{
console.log(result)//2
}).then(result=>{
console.log(result)//undefined
})
上面代码,最后一个输出undefined
是因为第二个then
方法没有返会值。
(2)finally()
finally
方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。
const pro1 = new Promise((resolve,reject)=>{
resolve(1)
})
pro1.then(result=>{
console.log(`结果${result}`) //结果1
return 3
}).then(result=>{
console.log(result)//3
}).catch(error=>{
console.log(error)
}).finally(()=>{
console.log("我一定会执行的")//我一定会执行的
})
4、Promise的静态成员
(1)resolve()
该方法返回一个resolved
状态的Promise,传递的数据作为状态数据。有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用。
const pro = Promise.resolve(1)
//等同于
const pro = new Promise((resolve,reject)=>{
resolve(1)
})
特殊情况:如果传递的数据是Promise,则直接返回传递的Promise对象
(2)reject()
该方法返回一个rejected
状态的Promise,传递的数据作为状态数据
const pro = Promise.reject(1)
//等同于
const pro = new Promise((resolve,reject)=>{
reject(1)
})
(3)all()
Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口。这个方法返回一个新的Promise对象,
返回的新的Promise对象的状态分成两种情况:
- 当参数中所有的Promise对象的状态都变成
fulfilled
,返回的Promise状态才会变成fulfilled
。此时参数的返回值组成一个数组,传递给新Promise的回调函数。 - 当参数之中有一个被
rejected
,新Promise的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给新Promise的回调函数。
//p1, p2, p3均是promise对象
const pro = Promise.all([p1, p2, p3]);
上述代码就是一个非常简单的调用方法
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.all(proms)
pro.then(res=>{
console.log("全部完成",res)
},err=>{
console.log(err,"错误")
})
上面代码是产生10个Promise对象,每个Promise对象都延迟时间1~5s中的随机时间后将状态推向已决,调用all方法,当10个Promise对象全部完成后再输出,或有一个错误的时候,也输出。
(4)race()
Promise.race()
方法同样是将多个 Promise 对象,包装成一个新的 Promise 对象。Promise.race()
方法的参数与Promise.all()
方法一样。
当参数里的任意一个Promise被成功或失败后,新Promise马上也会用参数中那个promise的成功返回值或失败详情作为参数调用新promise绑定的相应句柄,并返回该promise对象
function getRandom(min,max){
return Math.random() * (max-min) + min
}
const proms = []
for(let i=0;i<10;i++){
proms.push(new Promise((resolve,reject)=>{
setTimeout(() => {
if(Math.random()<0.1){
reject(i)
}else{
resolve(i)
console.log(i,"完成")
}
}, getRandom(1000,5000))
}))
}
const pro = Promise.race(proms)
pro.then(res=>{
console.log("有完成的",res)
},err=>{
console.log(err,"有错误")
})
console.log(proms)
上述代码和all()
例子一样,只不过,当有一个完成或失败的时候,就会执行pro
的then
或catch
回调函数
例子:异步加载图片
const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve();
image.onerror = reject();
image.src = path;
});
};
一旦图片加载完成,就会Promise状态就会发生变化。
5、async 和 await
async 和 await 是 ES2016 新增两个关键字,它们借鉴了 ES2015 中生成器在实际开发中的应用,目的是简化 Promise api 的使用,并非是替代 Promise。实际上就是生成器函数的一个语法糖。目的是简化在函数的返回值中对Promise的创建。
(1)async
async
函数返回一个 Promise 对象,可以使用then
方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async 用于修饰函数(无论是函数字面量还是函数表达式),放置在函数最开始的位置,被修饰函数的返回结果一定是 Promise 对象。
async function test(){
console.log(1);
return 2;//完成时状态数据
}
//等同于
function test(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
async function test(){
console.log(1)
return 2
}
const pro = test()
console.log(pro)//promise对象 promiseValue是2
注意:async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。
(2)await
正常情况下,await
命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。await
类似于生成器的yield
,当遇到await
的时候,就会等待await
后面的Promise对象执行完毕后再继续执行下面代码。
async function test(){
const namePro = await getName();//异步ajax
const passwordPro = await getPassword();
}
test()
函数执行过程中,会先等待getName()
执行完毕后,再执行getPassword()
注意:await
关键字必须出现在async
函数中
如果多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
async function test(){
let namePro = getName();
let passwordPro = getPassword();
let name = await namePro;
let password = await passwordPro;
}
先让getName()
和getPassword()
执行,然后再等待结果
await用在某个表达式之前,如果表达式是一个Promise,则得到的是thenable
中的状态数据。
async function test1(){
console.log(1);
return 2;
}
async function test2(){
const result = await test1();
console.log(result);
}
test2();
//等同于
function test1(){
return new Promise((resolve, reject)=>{
console.log(1);
resolve(2);
})
}
function test2(){
return new Promise((resolve, reject)=>{
test1().then(data => {
const result = data;
console.log(result);
resolve();
})
})
}
test2();
如果await
的表达式不是Promise,则会将其使用Promise.resolve包装后按照规则运行
async function test(){
const result = await 1
console.log(result)
}
//---等同
function test(){
return new Promise((resolve,reject)=>{
Promise.resolve(1).then(data =>{
const result = data
console.log(result)
resolve()
})
})
}