Promise源码分析(附带Promise的正确打开方式)

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的时候,会有一个状态,会有值,还会传入两个函数,resolvereject。那么我们实现一下:

constructor(executor) {
    //初始化
    this[PromiseStatus] = PENDING //当前状态
    this[PromiseValue] = undefined //当前值
    /**
     * 定义 resolve 函数
     * @param {*} data: 要返回的数据
     */
    const resolve = (data) => {
        //...
    }
    /**
     * 定义reject函数
     * @param {*} data: 要返回的数据
     */
    const reject = (data) => {
        //...
    }
    //执行
    executor(resolve, reject)
}

接下来,我们就要实现resolvereject函数的功能,其实这两个函数的功能非常简单,就是修改当前的状态和值,并且如果有任务队列,我们就执行任务队列中的内容。我把这两个函数的功能封装一下:

/**
 * 改变状态的函数
 * @param {*} data: 数据
 * @param {*} status: 要改变的状态,resolve 或 reject
 * @param {*} queue: 任务队列
 */
[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])

接下来我们要实现thencatch这两个后续处理函数,这两个函数实现什么功能想必大家都知道了,但我们要注意的是,如果调用then或者catch的时候,当前的状态已经是已决状态,那么就要直接执行。如果不是,那么就加入任务队列中,我在构造器中已经声明了 thenablescatchables 变量(thencatch函数的任务队列)

 //settled then处理函数
then(thenable, catchable) {
    //每个then都要返回一个新的promise
    return this[linkPromise](thenable, catchable)
}
//settled catch处理函数
catch (catchable) {
    return this[linkPromise](undefined, catchable)
}

因为功能相同,我进行了 2 次封装,都是哪两次封装呢?这是第一次,也就是实现加入任务队列的功能:

/**
 * then和catch的处理函数,分为两种情况,如果当前已经是已决状态,
 * 那么直接执行(此时直接执行也要加入事件队列中,无法模拟微队列,只能用宏队列实现下),如果当前还是未决状态, 
 * 那么把当前的处理函数加入相应的任务队列中
 * @param {*} handler 处理函数
 * @param {*} queue   处理队列
 */
[settledHandler](handler, status, queue) {
    //如果不是函数,那么直接返回
    if (typeof handler !== "function") return
    if (this[PromiseStatus] === status) {
        //如果已经是已决状态,直接执行
        setTimeout(() => {
            handler(this[PromiseValue])
        }, 0);
    } else {
        //处于未决状态,加入任务队列
        queue.push(handler)
    }
}

还有一次封装,也就是我们上面调用的 linkPromise 函数,为什么要调用这个函数?因为thencatch后续处理函数,都要返回一个新的Promise,我们什么时候知道,then 或 catch 函数执行了,该返回新的Promise呢?这里是比较难的一部分,我们可以将 settledHandler 也就是执行处理函数的功能,反正创建一个新的 Promise中执行:

/**
 * 用于创建一个新的Promise, 当我们调用then和catch处理函数时, 会返回一个新的Promise
 * @param {*} thenable
 * @param {*} catchable 
 */
[linkPromise](thenable, catchable) {
    /**
     * 返回一个新的Promise的状态处理,如果父级已经变为已决状态, 那么新的Promise也是已决状态
     * @param {*} data 
     * @param {*} handler 
     * @param {*} resolve 
     * @param {*} reject 
     */
    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成员

/**
 * 当数组中的每一个值都变为resolved时,返回新的promise的值resolve为一个数组,数组的内容为proms每个Promise的结果,如果有一个变为rejected, 那么直接结束
 * @param {*} proms 假定为一个数组
 */
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成员

/**
 * 当数组中有一个处于已决状态,那么结束
 * @param {*} proms: 假定是一个数组 
 */
static race(proms) {
    return new MyPromise((resolve, reject) => {
        proms.forEach(p => {
            p.then(data => resolve(data), err => reject(err))
        })
    })
}

resolve和reject静态成员

/**
 * 返回一个resolved状态的promise
 * @param {*} data 
 */
static resolve(data) {
    //如果穿过来的是一个Promise,直接返回就可以
    if (data instanceof MyPromise) {
        return data
    }
    return new MyPromise(resolve => resolve(data))
}
/**
 * 返回一个rejected状态的promise
 * @param {*} err 
 */
static reject(err) {
    return new MyPromise((resolve, reject) => {
        reject(err)
    })
}

Promise正确打开方式

1、异步处理的通用模型

ES6 将某一件可能发生异步操作的事情,分为两个阶段:unsettledsettled

  • 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)=>{
    /*
        未决阶段的处理
        通过调用resolve函数将Promise推向已决阶段的resolved状态
        通过调用reject函数将Promise推向已决阶段的rejected状态
        resolve和reject均可以传递最多一个参数,表示推向状态的数据
    */ 
    if(true){
        resolve()
    }else{
        reject()
    }
    
})
pro.then(data=>{
    /*
        这是thenable函数,如果当前的Promise已经是resolved状态,该函数会立即执行
        如果当前是未决阶段,则会加入到作业队列,等待到达resolved状态后执行
        data为状态数据
    */
},err=>{
    /*
    	then函数也可以填catchable函数,也可以不填。我们最好是通过catch方法添加catchable函数
    */
})
pro.catch(err=>{
   /*
        这是catchable函数,如果当前的Promise已经是rejected状态,该函数会立即执行
        如果当前是未决阶段,则会加入到作业队列,等待到达rejected状态后执行
        err为状态数据
    */
})

注意

  • Promise创建后会立即执行。

  • thenablecatchable函数是异步的,就算是立即执行,也会加入到事件队列中等待执行,加入的队列是微队列。

  • 在未决阶段的处理函数中,如果发生未捕获的错误,会将状态推向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也是异步的,他会加入到微队列中等同步代码执行完毕后再执行。随后输出bPromise函数执行完后,又执行剩下的同步代码输出c,同步代码执行完毕后,执行微队列中的输出resolve的结果值1,再执行宏队列中的setTimeout,输出b

3、Promise的方法then,catch,finally

(1)then()和catch()

then():注册一个后续处理函数,当Promise为resolved状态时运行该函数

catch():注册一个后续处理函数,当Promise为rejected状态时运行该函数

Promise对象中,无论是then方法还是catch方法,它们都具有返回值,返回的是一个全新的Promise对象,它的状态满足下面的规则:

  1. 如果当前的Promise是未决的,得到的新的Promise是进行中状态
  2. 如果当前的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 * 3result * 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对象的状态分成两种情况:

  1. 当参数中所有的Promise对象的状态都变成fulfilled,返回的Promise状态才会变成fulfilled。此时参数的返回值组成一个数组,传递给新Promise的回调函数。
  2. 当参数之中有一个被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()例子一样,只不过,当有一个完成或失败的时候,就会执行prothencatch回调函数

例子:异步加载图片

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()
        })
    })
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章