手撸一个Promise

318 阅读14分钟

前言

开始之前我们先了解一下以下几个概念

一、什么是宏任务与微任务?

Js 是单线程,但是一些高耗时操作就带来了进程阻塞问题。 为了解决这个问题,Js 有两种任务的执行模式:同步模式(Synchronous)异步模式(Asynchronous)。 在异步模式下,创建异步任务主要分为『宏任务』与『微任务』两种。

宏任务: 就是JS 内部(任务队列里)的任务,严格按照时间顺序压栈和执行,可以理解是每次执行栈执行的代码就是一个宏任务。 如: script(整体代码)setTimeoutsetIntervalI/OUI交互事件postMessageMessageChannelsetImmediate(Node.js 环境)

微任务: 通常来说就是需要在当前任务执行结束后立即执行的任务, 例如需要对一系列的任务做出回应,或者是需要异步的执行任务而又不需要分配一个新的任务, 这样便可以减小一点性能的开销。 如: promiseObject.observeMutationObserverprocess.nextTick(Node.js 环境)

二、EventLoop

事件循环机制,是指浏览器Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用「异步」的原理 由三部分组成:

  1. 调用栈
  2. 微任务队列
  3. 消息队列(宏任务队列)
运行机制

在事件循环中,每进行一次循环操作称为tick,每一次tick的任务处理模型是比较复杂的,但关键步骤如下:

  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取) 如图:

绘图1.png

三、发布-订阅者模式

发布订阅者模式其实是一种对象间一对多的依赖关系(利用消息队列
定义: 当一个对象的状态(state)发生改变时,所有依赖于它的对象都得到状态改变通知订阅者(Subscriber)把自己想订阅的事件注册调度中心(Event Channel),当发布者(Pubblisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码

实现思路:

    · 创建一个类
    · 该类上创建一个缓存列表
    · on方法用来吧函数Fn都加到缓存列表(调度中心)
    · emit方法取到event事件类型,根据event值去执行对应缓存列表中的函数(发布者发布事件到调度中心,调度中心处理代码)
    · off方法可以根据event事件类型取消订阅
class Observer {
    constructor() {
        // 消息队列
        this.message = {}
    }
    // 向消息队列中添加事件类型、事件函数
    on(type, fn){
        // 没有当前事件类型,初始化一个当前类型
        if(!this.message[type]){ 
            this.message[type] = []
        }
        // 当前事件类型存在,将fn添加到该类型下
        this.message[type].push(fn)
    }
    // 向消息队列内的事件发送执行
    emit(type){
        if(!this.message[type]) return
        this.message[type].forEach(element => {
            element()
        });
    }
    // 删除消息队列中的事件类型或某个事件函数
    off(type, fn){
        // 1、判断是否有当前事件类型
        if(!this.message[type]) return
        // 2、判断是否有fn参数,没有fn则直接删除当前事件类型下的所有事件函数
        if(!fn){
            delete this.message[type]
            return
        }
        // 3、删除消息队列中该类型下的与当前fn相同的事件函数
        this.message[type] = this.message[type].filter(item => item != fn)
    }
}
// 向person委托一些内容
const person = new Observer()
person.on('abc', fn1)
person.on('def', fn2)
person.on('abc', fn3)
person.off('abc', fn1)
person.emit('abc')
console.log(person)

function fn1(){
    console.log('fn1')
}
function fn2(){
    console.log('fn2')
}
function fn3(){
    console.log('fn3')
}

// 执行结果: Observer { message: { def: [ [Function: fn2] ] } }

实现手写promise

咱们先看一下原生promise的执行和基本属性,上代码:

let promise = new Promise((resolve, reject) => {
    resolve('fulfilled');     // 成功状态 fulfilled
    reject('rejected');       // 失败状态 rejected 
    throw new Error('error')  // 异常抛出,返回结果为:失败状态 rejected
})

promise.then((value) => { // then方法中提供两个参数 1. 成功回调 2.失败的回调
    console.log(value, 'success')  // 会把成功的结果返回
}, (reason) => {
    console.log(reason, 'fail')    // 会把失败的结果返回
})

执行结果为:fulfilled success

这里暴露出了四个知识点:

  • 1、执行了resolve,Promise状态会变成fulfilled
  • 2、执行了reject,Promise状态会变成rejected
  • 3、Promise只以第一次为准,第一次成功就永久fulfilled,第一次失败就永远状态为rejected
  • 4、Promise中除了调用resolvereject能改变状态外,还可以用throw抛出异常,也会执行到reject的失败逻辑

一、实现resolve、reject、then方法

咱们先来确定promise的几个必要因素:

    1Promise会有三个状态:
            pending: 等待状态
            fulfilled: 成功状态
            rejected: 失败状态
    2Promise中的函数会立即执行、那就会用到 executor函数
    3、会有resolve,reject两个方法,并且会在这两个方法中
    4then()方法中会根据当前状态调用resolve,reject的执行的结果,并且进行返回
    5throw抛出异常,也会执行到reject的失败逻辑
// 初始化三种状态
const PENDING = 'pending'; // 默认等待态
const FULFILLED = 'fulfilled'; // 成功态 
const REJECTED = 'rejected'; // 失败态
// 创建Promise类
class Promise {
    constructor(executor) { // executor 会默认被执行  同步执行
        this.status = PENDING;    // 状态
        this.value = undefined;   // 成功的结果
        this.reason = undefined;  // 失败的结果
        const resolve = (value) => {
            this.value = value; // 这里保存le成功状态的结果
            this.status = FULFILLED // 同时对状态进行修改
        }
        const reject = (reason) => {
            this.reason = reason; // 这里保存了失败状态的结果
            this.status = REJECTED;
        }
        // 如果执行时出错,我们将错误传递到reject中,执行到了失败的逻辑
        try {
            executor(resolve, reject); // 默认new Promise中的函数会立即执行
        } catch (e) { 
            reject(e)
        }
    }
    // onFulfilled: 成功的回调  onRejected:失败的回调结果
    then(onFulfilled, onRejected) {
        if (this.status == FULFILLED) { // 状态为成功时,将返回成功结果的值
            onFulfilled(this.value)
        }
        if (this.status == REJECTED) { //  状态为失败时,将返回失败结果的值
            onRejected(this.reason);
        }
    }
}

测试一下代码:

let promise1 = new Promise((resolve, reject) => {
    resolve('fulfilled');    
})
promise1.then((value) => {
    console.log(value, 'success')  
}, (reason) => {
    console.log(reason, 'fail')
})
// 执行结果为: fulfilled,fail

let promise2 = new Promise((resolve, reject) => {
    reject('rejected');      
})
promise2.then((value) => {
    console.log(value, 'success')  
}, (reason) => {
    console.log(reason, 'fail')
})
// 执行结果为: rejected,fail

let promise3 = new Promise((resolve, reject) => {
    resolve('fulfilled');  
    reject('rejected');      
})
promise3.then((value) => {
    console.log(value, 'success')  
}, (reason) => {
    console.log(reason, 'fail')
})
// 执行结果为: rejected,fail

二、状态不可变

这里就有问题了,上边代码中,promise3正确的返回值应该为:fulfilled,success而不是当前的【rejected,fail】,因为这里调用了两次之后,第一次修改的状态为成功,第二次又进行了修改,修改为了失败。Promise的状态应该为:状态一旦确定不在进行改变,所有这里就要进行优化,当状态为pending的时候才能对状态进行修改,其他状态不可修改!

const resolve = (value) => {
    if(this.status === PENDING){ // 当状态为pending的时候才能修改
        this.value = value
        this.status = FULFILLED
    }
}
const reject = (reason) => {
    if(this.status === PENDING){ // 当状态为pending的时候才能修改
        this.reason = reason
        this.status = REJECTED
    }
}

到这里一个丐版的Promise已经实现了,但是这还有很多问题,来我们接着往下走

三、定时器问题

我们先来看一段代码

let promise = new Promise((resolve, reject) => {
    // 我在这加了一个setTimeout
    setTimeout(() => {
        resolve('fulfilled') // 让promise变成成功态
        reject('rejected')   // 让promise变成失败态
    }, 1000);
})

promise.then((value) => { 
    console.log(value, 'success') // 成功后返回值
}, (reason) => {
    console.log(reason, 'fail')   // 失败后返回值
})

// 执行结果为: 空  没反应

在Promise中加了一个定时器后,输出结果为空(无反应),这是什么原因呢......,在.then() 的时候呢,Promise的状态还没改变,还是Pending状态,一秒以后状态才发生了变化,然后才有了返回结果,所以当我直接执行.then()方法的时候输出为空的结果。

这块呢就涉及一个设计模式了:发布订阅模式。需要将then中的两个回调函数:onFulfilled和onRejected先进行存储,当执行resolve方法或者reject方法,执行完成以后再执行onFulfilled和onRejected回调

const PENDING = 'pending'; // 默认等待态
const FULFILLED = 'fulfilled'; // 成功态 
const REJECTED = 'rejected'; // 失败态
class Promise {
    constructor(executor) { // executor 会默认被执行  同步执行
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        // 用户调用resolve和reject 可以将对应的结果暴露在当前的promise实实例上
        this.onResolvedCallbacks = [];  // 存储then成功回调的函数集合
        this.onRejectedCallbacks = [];  // 存储then失败回调的函数集合
        const resolve = (value) => {
            if (this.status == PENDING) {
                this.value = value;
                this.status = FULFILLED
                // 调用了成功的逻辑
                this.onResolvedCallbacks.forEach(fn=>fn())
            }
        }
        const reject = (reason) => {
            if (this.status == PENDING) {
                this.reason = reason;
                this.status = REJECTED;
                // 调用了失败的逻辑
                this.onRejectedCallbacks.forEach(fn=>fn())
            }
        }
        try {
            executor(resolve, reject); // 默认new Promise中的函数会立即执行
        } catch (e) { // 如果执行时出错,我们将错误传递到reject中 -》 执行到了失败的逻辑
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        if (this.status == FULFILLED) {
            onFulfilled(this.value)
        }
        if (this.status == REJECTED) {
            onRejected(this.reason);
        }
        if (this.status == PENDING) { // 处理异步状态下的回调
            this.onResolvedCallbacks.push(() => {
                // todo  可以实现其他逻辑
                onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                // todo  可以实现其他逻辑
                onRejected(this.reason);
            })
        }
    }
}

测试一下:

 let promise = new Promise((resolve, reject) => {
    // 我在这加了一个setTimeout
    setTimeout(() => {
        resolve('fulfilled') // 让promise变成成功态
        reject('rejected')   // 让promise变成失败态
    }, 1000);
})

promise.then((value) => { 
    console.log(value, 'success') // 成功后返回值
}, (reason) => {
    console.log(reason, 'fail')   // 失败后返回值
})

// 执行结果为: fulfilled  success

就很完美啊

四、then链式调用

  • then方法是支持链式调用的,链式调用的好处就是可以解决回调地狱的问题
  • then方法中,成功的回调或者失败的回调返回的是一个promise,那么会采用返回的promise的状态,走外层下一次then中的成功或失败, 同时将promise处理后的结果向下传递
  • then方法中 成功的回调或者失败的回调返回的是一个普通值 (不是promise)这里会将返回的结果传递到下一次then的成功中去,并将值传递下去
  • 如果在then方法中 成功的回调或者失败的回调 执行时出错会走到外层下一个then中的失败中去

举个小🌰

const promise = new Promise((resolve, reject)=>{
    resolve('ok')
})
promise.then((res)=>{
    console.log(res, 'fulfilled') // 打印结果: ok fulfilled   
    // 返回了一个新的Promise,该Promise的状态会直接影响下一个then的回调结果
    return new Promise((resolve, reject)=>{
        reject('fail')
    })
}, (err)=>{
    console.log(err, 'rejected')
}).then((res)=>{
    console.log(res, 'fulfilled-1') // 打印结果: fail rejected-1
}, (err)=>{
    console.log(err, 'rejected-1')
})

如果返回的是一个失败的Promise或者报错了,才会走下一个then的失败,否则全部走成功

那如何实现一个链式调用呢?
return new Promise() 每次都产生一个全新的Promise来保证状态可以正常的切换,不会相互影响,让我们来看一下代码是怎么实现的:

then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled == 'function'? onFulfilled:v=>v;
        onRejected = typeof onRejected == 'function' ? onRejected : e=>{throw e};
        // 每次调用then方法 都必须返回一个全新的promise
        let promise2 = new Promise((resolve, reject) => { 
            if (this.status == FULFILLED) {
                // 这块为什么包一个定时器?
                // 解决微任务,立即执行的问题
                setTimeout(() => {
                    try {
                        // x 就是上一个then成功或者失败的返回值,这个x决定proomise2 走成功还是走失败
                        let x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status == REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status == PENDING) {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(x, promise2, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                })
            }
        });
        return promise2;
    }

这里呢有一个X值的判断逻辑,需要判断当前返回值X的类型

    1.如果x是一个普通值,则直接调用resolve即可
    2.如果x是一个promise那么应该采用这个promise的状态 决定调用的是 resolve还是reject
function resolvePromise(x, promise2, resolve, reject) { // 我们还需要考虑 x 可能是别人家的promise
    // 希望我的promise可以和别人的promise一起来混用的 q库 bluebird库
    // If promise and x refer to the same object, reject promise with a TypeError as the reason
    if (x === promise2) {
        return reject(new TypeError('循环引用'))
    }
    // 继续判断x 是不是一个promise  promsise需要有then方法 (啥时候是函数的? 别人写的proimise 就有可能是函数)
    if ((typeof x === 'object' && x !== null) || (typeof x == 'function')) {
        // 才有可能是一个promise, 继续判断x 是否有then
        // Let then be x.then
        let called = false;
        try {
            let then = x.then; // 尝试取then方法  
            if (typeof then == 'function') { // 我就认为他是promise了
                // x.then // 这个会再次取一次属性 触发get方法
                // then.call(x) // 这个不会
                then.call(x, (y) => { // y有可能还是一个promise ,所以要再次进行解析流程
                    // 我需要不停的解析成功的promise中返回的成功值,直到这个值是一个普通值
                    if(called) return;
                    called = true
                    resolvePromise(y,promise2,resolve,reject);
                }, (r) => {
                    if(called) return;
                    called = true
                    reject(r);
                });
            } else { // {then:1}
                resolve(x);
            }
        } catch (e) {
            if(called) return;
            called = true
            reject(e); // 让promise2 变成失败态
        }
    } else {
        // x 是一个普通值 
        resolve(x);
    }
}

五、Promise.all()

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果Promise全都成功,则返回成功结果数组
  • 如果有一个Promise失败,则返回这个失败结果

咱们先来看一下原生的用法:

let promise1 = new Promise((resolve, reject) => {
    resolve('ok-1')
})
let promise2 = new Promise((resolve, reject) => {
    resolve('ok-2')
})
// let promise3 = new Promise((resolve, reject) => {
//     reject('fail-1')
// })
Promise.all([promise1, promise2, 34]).then((res)=>{
    console.log('成功', res)
}, (err)=>{
    console.log('失败', err)
})
// 打印结果为:成功 [ 'ok-1', 'ok-2', 34 ]
// promise3放开后: 失败 fail-1

上边了解的promise.all的特性后然后咱们就来看一下源码实现吧:

static all(promises) { 
    // 将数组中的promise依次执行
    const result = []
    let count = 0 
    return new Promise((resolve, reject) => { 
        const process = (index, value) => { 
            result[index] = value count++ 
            // 解决多个异步并发问题,只能靠计数器
            if (count === promises.length) resolve(result) 
        } 
        promises.forEach((promise, index) => { 
            if (promise && typeof promise.then === 'function') { // 异步的
                promise.then(res => { 
                    process(index, res) 
                }, err => reject(err))
            } else { //同步的
                process(index, promise) 
            } 
        }) 
    }) 
}

六、Promise.any()

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 如果有一个Promise成功,则返回这个成功结果
  • 如果所有Promise都失败,则报错 老规矩,先上any原生使用方法,看一下执行结果:
let promise1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-1')
    }, 1000)
})
let promise2 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-2')
    }, 500)
})
let promise3 = new Promise((resolve, reject) => {
    reject('fail-1')
})
Promise.any([promise1, promise2, promise3, 34]).then((res)=>{
    console.log('成功', res)
}, (err)=>{
    console.log('失败', err)
})
// 当有非promise项,且其他pormise项有延迟返回时会直接将非promise项返回: 打印结果: 成功 34
// 仅有promise项时,只要有一个promise成功然后返回相应的返回结果:打印结果:成功 ok-2
// 如果全部失败,则会报错: 失败 AggregateError: All promises were rejected

手撸一个any:

static any(promises) { 
    return new Promise((resolve, reject) => { 
        let count = 0 
        promises.forEach((promise) => { 
            promise.then(val => { 
                resolve(val) 
            }, err => { 
                count++ 
                if (count === promises.length) { 
                    reject(new AggregateError('All promises were rejected')) 
                } 
            }) 
        }) 
    }) 
}

七、Promise.race()

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 返回结果以最快执行完的那个Promise的结果为准,无论成功失败 原生用法:
let promise1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-1')
    }, 1000)
})
let promise2 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-2')
    }, 500)
})
let promise3 = new Promise((resolve, reject) => {
    reject('fail-1')
})
Promise.race([promise1, promise2, promise3, 34]).then((res)=>{
    console.log('成功', res)
}, (err)=>{
    console.log('失败', err)
})
// 返回结果以最快执行完的结果为准: 打印结果 失败 fail-1

race的源码实现:

 static race(promises) { 
     return new Promise((resolve, reject) => { 
         promises.forEach(promise => { 
             if (promise && typeof promise.then === 'function') { 
                 promise.then(res => { 
                     resolve(res) 
                 }, err => { 
                     reject(err) 
                 }) 
              } else { 
                  resolve(promise) 
              } 
        }) 
    }) 
}

八、Promise.allSettled()

  • 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
  • 把每一个Promise的结果,集合成数组返回,返回的数组里是所有promise及非promise结果的集合
  • 它不同于all的是:返回结果不相互依赖 🌰
let promise1 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-1')
    }, 1000)
})
let promise2 = new Promise((resolve, reject) => {
    setTimeout(()=>{
        resolve('ok-2')
    }, 500)
})
let promise3 = new Promise((resolve, reject) => {
    reject('fail-1')
})
Promise.allSettled([promise1, promise2, promise3, 34]).then((res)=>{
    console.log('成功', res)
}, (err)=>{
    console.log('失败', err)
})

实现源码,展示:

static allSettled(promises) { 
    return new Promise((resolve, reject) => { 
        const res = [] 
        let count = 0 
        const process = (status, value, i) => {
            res[i] = { status, value } 
            count++ 
            if (count === promises.length) { 
                resolve(res) 
            }
        } 
        promises.forEach((promise, i) => { 
            if ((promise && typeof promise.then === 'function') { 
                promise.then(res => { 
                    process('fulfilled', res, i) 
                }, err => { 
                    process('rejected', err, i) 
                }) 
            } else { 
                process('fulfilled', promise, i) 
            } 
        }) 
    }) 
}

九、Promise.prototype.finally()

  • finally是promise无论成功失败都会执行的方法
  • finally没有参数,他会将promise的状态及结果向下传递 举个🌰
let promise = new Promise((resolve, reject)=>{
    resolve('fulfilled')
    //reject('rejected')
})

promise.finally(()=>{
    console.log('无论成功失败都执行')
}).then((res)=>{
    console.log('成功', res)
},(err)=>{
    console.log('失败', err)
})

/*
* resolve: 执行结果为  
*           <1>无论成功失败都执行
*           <2>成功 fulfilled
*
* reject:  执行结果为
*          <1>无论成功失败都执行
*          <2>失败 rejected
*/

finally的实现:

  • finally里返回的会是一个then,同时将结果传给下一个then
Promise.prototype.finally = function (cb) {
    return this.then((y)=>{
        return Promise.resolve(cb()).then((d)=>y);
    },(r)=>{
         // cb执行一旦报错 就直接跳过后续的then的逻辑,直接将错误向下传递
      return  Promise.resolve(cb()).then(()=> {throw r})
    })
}

测试代码:

Promise.reject('ok').finally(()=>{ // finally 如果返回的是一个promise那么会有等待效果
    console.log('无论成功失败都执行')
    return new Promise((resolve,reject)=>{
        setTimeout(() => {
            resolve('xxxxxxxxxxx'); // 如果是失败 会用这里的失败作为失败的原因
        }, 1000);
    });
}).then((data)=>{
    console.log('成功',data)
}).catch(err=>{
    console.log('失败',err)
});

//执行结果为: 
//   无论成功失败都执行
//   失败 ok

Promise基本的使用和源码实现已经实现的七七八八了,搞懂这些promise的面试及日常使用基本就没有啥问题了,加油!