万字详解JavaScript手写一个Promise

目录
  • 前言
  • Promise核心原理实现
    • Promise的使用分析
    • MyPromise的实现
  • 在Promise中加入异步操作
  • 实现then方法的多次调用
  • 实现then的链式调用
  • then方法链式调用识别Promise对象自返回
  • 捕获错误及 then 链式调用其他状态代码补充
    • 捕获执行器错误
    • 捕获then中的报错
    • 错误与异步状态的链式调用
  • 将then方法的参数变成可选参数
  • Promise.all方法的实现
  • Promise.resolve方法的实现
  • finally方法的实现
  • catch方法的实现
  • 完整代码

前言

手写Promise现在已经成了面试的热门内容,但在实际开发中基本都不会去手写一个Promise,但是在面试中各种手写题可能就会遇到一个手写Promise,我们可以尽量提高我们的上限,从而获取更多的工作机会。

Promise核心原理实现

首先我们从使用的角度来分析一下Promise,然后编写一个最简单版本的Promise。

Promise的使用分析

Promise就是一个类,在执行这个类的时候,需要传递一个执行器(回调函数)进去,执行器会立即执行。

Promise中的状态分为三个,分别是:

  • pending→等待
  • fulfilled→成功
  • rejected→失败

状态的切换只有两种,分别是:

  • pending→fulfilled
  • pending→rejected

一旦状态发生改变,就不会再次改变:

  • 执行器中的两个参数,分别是resolve和reject,其实就是两个回调函数,调用resolve是从pending状态到fulfilled,调用reject是从状态pending到rejected。传递给这两个回调函数的参数会作为成功或失败的值。
  • Promise的实例对象具有一个then方法,该方法接受两个回调函数,分别来处理成功与失败的状态,then方法内部会进行判断,然后根据当前状态确定调用的回调函数。then方法应该是被定义在原型对象中的。
  • then的回调函数中都包含一个值,如果是成功,表示成功后返回的值;如果是失败就表示失败的原因。

MyPromise的实现

根据我们上面的分析,写出如下代码:

MyPromise.js
// 定义所有状态的常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise实质上就是一个类,首先创建一个Promise的类
class MyPromise {
    // 实例化Promise时需要一个回调函数,该回调函数立即执行
    constructor(executor) {
        // 在调用executor需要传递两个回调函数,分别是resolve和reject
        executor(this.resolve, this.reject)
    }
    // Promise 的状态
    status = PENDING
    // 记录成功与失败的值
    value = undefined
    reason = undefined
    resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
        // 形参value表示,调用resolve时传递的参数
        // 如果当前状态不是pending,就直接跳出该逻辑
        if (this.status !== PENDING) return

        // 将状态修改为成功
        this.status = FULFILLED

        // 将传入的值进行保存
        this.value = value
    }
    reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
        // 形参reason表示,调用reject时传递的失败的原因
        // 如果当前状态不是pending,就直接跳出该逻辑
        if (this.status !== PENDING) return

        // 将状态修改为失败
        this.status = REJECTED

        // 保存失败的原因
        this.reason = reason
    }
    // then方法的实现
    then (onFulfilled, onRejected) {
        // 判断当前状态,根据状态调用指定回调
        if (this.status === FULFILLED) {
            // 将成功的值作为参数返回
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            // 将失败的原因作为参数返回
            onRejected(this.reason)
        }
    }
}
// 导出Promise
module.exports = MyPromise 

现在我们就来写一段代码验证一下上面的代码

验证resolve:

const MyPromise = require('./myPromise')

let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 输出
    成功
*/

验证reject:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    reject('失败')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 输出
    失败
*/

验证状态不可变:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
    reject('失败')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})
/* 输出
    成功
*/ 

在Promise中加入异步操作

如果我们的代码中存在异步操作,我们自己写的Promise将毫无用处,

例如下面这段代码:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    setTimeout(resolve, 2000, '成功')
})
promise.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);
})

这段代码2000ms后没有任何输出,为了解决这个问题我们将对自己编写的类进行如下操作:

  • 创建两个实例方法用于存储我们传入的成功与失败的处理逻辑。
  • then方法添加状态为pending时的处理逻辑,这时将传递进来的属性保存到实例上。
  • 在成功或者失败时调用相应的传递进来的回调函数(实例属性存在函数的情况下)。

实现代码如下:

// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    value = undefined
    reason = undefined
    // 存储成功与失败的处理逻辑
    onFulfilled = undefined
    onRejected = undefined
    resolve = (value) => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value
        // 如果将状态修复为成功,调用成功的回调
        this.onFulfilled && this.onFulfilled(this.value)
    }
    reject = (reason) => {
        if (this.status !== PENDING) return
        this.status = REJECTED
        this.reason = reason
        // 如果将状态修复为失败,调用失败的回调
        this.onRejected && this.onRejected(this.reason)
    }
    then (onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
            this.onFulfilled = onFulfilled
            this.onRejected = onRejected
        }
    }
}
module.exports = MyPromise

这里的this.onFulfilled && this.onFulfilled(this.value)表示如果该属性存在就调用这个方法。

现在我们重新执行一开始上面那一段代码,2s后会成功输出成功

实现then方法的多次调用

Promise实例中存在要给then方法,允许我们在Promise实例中链式调用,每个then方法还会返回一个Promise实例,

如下图所示:

Promise实例方法then是可以多次进行调用,而我们现在自己封装的却执行调用一次,现在根据新需要来重新改写我们的代码,

实现思路如下:

  • 定义可以存储多个回调的数组,用于存储多个回调函数。
  • 如果是同步执行的代码,执行后立即知道执行结果,所以可以直接调用回调函数。
  • 如果是异步代码,需要将每次回调函数保存到数组中,然后状态变化时依次调用函数。

实现代码如下:

// MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    status = PENDING
    value = undefined
    reason = undefined
    // 存储成功与失败的处理逻辑
    onFulfilled = []
    onRejected = []

    resolve = (value) => {
        if (this.status !== PENDING) return
        this.status = FULFILLED
        this.value = value

        // 如果将状态修复为成功,调用成功的回调
        while (this.onFulfilled.length) {
            // Array.prototype.shift() 用于删除数组第一个元素,并返回
            this.onFulfilled.shift()(this.value)
        }
    }
    reject = (reason) => {
        if (this.status !== PENDING) return
        this.status = REJECTED
        this.reason = reason
        // 如果将状态修复为失败,调用失败的回调
        while (this.onRejected.length) {
            // Array.prototype.shift() 用于删除数组第一个元素,并返回
            this.onRejected.shift()(this.reason)
        }
    }
    then (onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        } else if (this.status === REJECTED) {
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
            this.onFulfilled.push(onFulfilled)
            this.onRejected.push(onRejected)
        }
    }
}
module.exports = MyPromise

这里我们通过数组的shift()方法,该方法删除数组的第一个元素,并返回,返回的这个值正好是一个回调函数,然后调用该函数即可实现该功能。

实现then的链式调用

想要实现then的链式调用,主要解决两个问题:

  • 返回的是一个新的MyPormise实例。
  • then的返回值作为下一次的链式调用的参数。

这里分为两种情况:

  • 直接返回一个值,可以直接作为值使用
  • 返回一个新的MyPormise实例,此时就需要判断其状态

实现代码如下:

// MyPromise.js
/* 省略的代码同上 */
class MyPromise {
    /* 省略的代码同上 */

    // then方法的实现
    then (onFulfilled, onRejected) {
        // then 方法返回一个MyPromise实例
        return new MyPromise((resolve, reject) => {
            // 判断当前状态,根据状态调用指定回调
            if (this.status === FULFILLED) {
                // 将成功的值作为参数返回
                // 保存执行回调函数的结果
                const result = onFulfilled(this.value)

                // 如果返回的是一个普通的值,直接调用resolve
                // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
                resolvePromise(result, resolve, reject)
            } else if (this.status === REJECTED) {
                // 将失败的原因作为参数返回
                onRejected(this.reason)
            } else {
                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
                this.onFulfilled.push(onFulfilled)
                this.onRejected.push(onRejected)
            }
        })
    }
}
function resolvePromise (result, resolve, reject) {
    // 判断传递的result是不是MyPromise的实例对象
    if (result instanceof MyPromise) {
        // 说明是MyPromise的实例对象
        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
        // result.then(value => resolve(value), reason => reject(reason))
        // 简写
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

then方法链式调用识别Promise对象自返回

在Promise中,如果then方法返回的是自己的promise对象,则会发生promise的嵌套,这个时候程序会报错,

测试代码如下:

var promise = new Promise((resolve, reject) => {
  resolve(100)
})
var p1 = promise.then(value => {
  console.log(value)
  return p1
})
// 100
// Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

想要解决这个问题其实我们只需要在then方法返回的MyPromise实例对象与then中回调函数返回的值进行比对,如果相同的返回一个reject的MyPromise实例对象,并创建一个TypeError类型的Error。

实现代码如下:

// MyPromise.js
    /* 省略的代码同上 */
    then (onFulfilled, onRejected) {
        // then 方法返回一个MyPromise实例
        const promise = new MyPromise((resolve, reject) => {
            // 判断当前状态,根据状态调用指定回调
            if (this.status === FULFILLED) {
                // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
                // 如果不变成异步的话是在函数内获取不到promise的
                setTimeout(() => {
                    // 将成功的值作为参数返回
                    // 保存执行回调函数的结果
                    const result = onFulfilled(this.value)

                    // 如果返回的是一个普通的值,直接调用resolve
                    // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
                    resolvePromise(promise, result, resolve, reject)
                }, 0)
            } else if (this.status === REJECTED) {
                // 将失败的原因作为参数返回
                onRejected(this.reason)
            } else {
                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
                this.onFulfilled.push(onFulfilled)
                this.onRejected.push(onRejected)
            }
        })
        return promise
    }
}
function resolvePromise (promise, result, resolve, reject) {
    // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。
    if (promise === result) {
        // 这里调用reject,并抛出一个Error
        // return 是必须的,阻止程序向下执行
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 判断传递的result是不是MyPromise的实例对象
    if (result instanceof MyPromise) {
        // 说明是MyPromise的实例对象
        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
        // result.then(value => resolve(value), reason => reject(reason))
        // 简写
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

这里then方法中的setTimeout的作用并不是延迟执行,而是为了调用resolvePromise函数时,保证创建的promise存在。

捕获错误及 then 链式调用其他状态代码补充

到目前为止我们现实的Promise并没有对异常做任何处理,为了保证代码的健壮性,我们需要对异常做一些处理。

捕获执行器错误

现在我们需要对执行器进行异常捕获,如果发生异常,就将我们的状态修改为rejected

捕获执行器的错误也比较简单,只需要在构造函数中加入try...catch语句就可以,

实现代码如下:

    constructor(executor) {
        try {
            // 在调用executor需要传递两个回调函数,分别是resolve和reject
            executor(this.resolve, this.reject)
        } catch (e) {
            // 发生异常调用reject
            this.reject(e)
        }
    }

测试代码如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    throw new Error('执行器错误')
})

promise.then(value => {
    console.log(value);
}, error => {
    console.log(error.message);
})
/* 输出
    执行器错误
*/

捕获then中的报错

现在我们需要对then中的异常捕获到,并在下一次链式调用中传递到then的第二个函数中,实现的方式也是通过try...catch语句,

示例代码如下:

// then方法的实现
then (onFulfilled, onRejected) {
    // then 方法返回一个MyPromise实例
    const promise = new MyPromise((resolve, reject) => {
        // 判断当前状态,根据状态调用指定回调
        if (this.status === FULFILLED) {
            // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
            // 如果不变成异步的话是在函数内获取不到promise的
            setTimeout(() => {
                try {
                    // 将成功的值作为参数返回
                    // 保存执行回调函数的结果
                    const result = onFulfilled(this.value)

                    // 如果返回的是一个普通的值,直接调用resolve
                    // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
                    resolvePromise(promise, result, resolve, reject)
                } catch (error) {
                    reject(error)
                }
            }, 0)
        } else if (this.status === REJECTED) {
            // 将失败的原因作为参数返回
            onRejected(this.reason)
        } else {
            // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
            this.onFulfilled.push(onFulfilled)
            this.onRejected.push(onRejected)
        }
    })
    return promise
}

测试代码如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    resolve('成功')
})
// 第一个then方法中的错误要在第二个then方法中捕获到
promise.then(value => {
    console.log('resolve', value)
    throw new Error('then的执行过程中遇到异常')
}).then(null, reason => {
    console.log(reason.message)
})

/* 输出
    resolve 成功
    then的执行过程中遇到异常
*/

错误与异步状态的链式调用

现在只对成功状态的then进行的链式调用以及错误处理,错误与异步状态未进行处理,其实处理起来也是一样的,

示例代码如下:

    // then方法的实现
    then (onFulfilled, onRejected) {
        // then 方法返回一个MyPromise实例
        const promise = new MyPromise((resolve, reject) => {
            // 判断当前状态,根据状态调用指定回调
            if (this.status === FULFILLED) {
                // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
                // 如果不变成异步的话是在函数内获取不到promise的
                setTimeout(() => {
                    try {
                        // 将成功的值作为参数返回
                        // 保存执行回调函数的结果
                        const result = onFulfilled(this.value)

                        // 如果返回的是一个普通的值,直接调用resolve
                        // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else if (this.status === REJECTED) {
                // 失败的处理同成功处理,只是调用的回调函数不同
                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason)
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else {
                this.onFulfilled.push((value) => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(value)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
                this.onRejected.push((reason) => {
                    setTimeout(() => {
                        try {
                            const result = onRejected(reason)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
            }
        })
        return promise
    }

测试代码如下:

const MyPromise = require('./myPromise')
let promise = new MyPromise((resolve, reject) => {
    setTimeout(resolve, 2000, '成功')
})
// 第一个then方法中的错误要在第二个then方法中捕获到
promise.then(value => {
    console.log('resolve', value)
    throw new Error('then的执行过程中遇到异常')
}).then(null, reason => {
    console.log(reason.message)
})
/* 输出
    resolve 成功
    then的执行过程中遇到异常
*/

将then方法的参数变成可选参数

Promise中的then方法其实是两个可以可选参数,如果我们不传递任何参数的话,里面的结果是向下传递的,直到捕获为止,

例如下面这段代码:

new Promise((resolve, reject) => {
    resolve(100)
})
    .then()
    .then()
    .then()
    .then(value => console.log(value))
// 最后一个then输入100 

这段代码可以理解为:

new Promise((resolve, reject) => {
    resolve(100)
})
    .then(value => value)
    .then(value => value)
    .then(value => value)
    .then(value => console.log(value))

所以说我们只需要在没有传递回调函数时,赋值一个默认的回调函数即可。

实现代码如下:

// then方法的实现
then (onFulfilled, onRejected) {
    // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递
    onFulfilled = onFulfilled ? onFulfilled : value => value
    // 同理
    onRejected = onRejected ? onRejected : reason => { throw reason }
    // then 方法返回一个MyPromise实例
    const promise = new MyPromise((resolve, reject) => {
        // 判断当前状态,根据状态调用指定回调
        if (this.status === FULFILLED) {...
        } else if (this.status === REJECTED) {...
        } else {...
        }
    })
    return promise
}

Promise.all方法的实现

关于all()方法的使用,可以参数Promise.all()。简单的说Promise.all()会将多个Promise实例包装为一个Promise实例,且顺序与调用顺序一致,

示例代码如下:

function p1 () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}
function p2 () {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('p2')
        }, 0)
    })
}
Promise.all(['a', 'b', p1(), p2(), 'c']).then(result => {
    console.log(result)
    // ["a", "b", "p1", "p2", "c"]
})

在这段代码中,我们的p1的执行是延迟了2s的,这里如果不使用Promise.all()的话最终顺序是与我们调用不同的。

现在我们来分析一下all()的实现思路:

  • all()方法可以通过类直接调用,所以是一个静态方法
  • all()方法接收一个数组,数组中的值可以是一个普通值,也可以是一个MyPromise的实例对象
  • return一个新的MyPromise实例对象
  • 遍历数组中的每一个值,判断值得类型,如果是一个普通值得话直接将值存入一个数组;如果是一个MyPromise的实例对象的话,会调用then方法,然后根据执行后的状态,如果失败的话调用新的MyPromise实例对象中的rejecte,如果是成功话将这个值存入一个数组
  • 存入数组时计数,如果存入的数量达到传入的数组长度,说明调用完毕,执行resolve并将最终的结果数组作为参数返回。

实现代码:

/**
 * @description: 将多个Promise实例合并为一个Promise实例
 * @param {*} array Promise或者普通值
 * @returns {Promise}
 */
static all (array) {
    // 用于存放最终结果的数组
    let result = []
    // 用于计算当前已经执行完的实例的数量
    let count = 0
    // 最后返回的是一个Promise实例
    return new MyPromise((resolve, reject) => {
        /**
         * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve
         * @param {*} result 存放结果的数组
         * @param {*} index 要加入的索引
         * @param {*} value 要加入数组的值
         * @param {*} resolve Promise中的resolve
         */
        function addResult (result, index, value, resolve) {
            // 根据索引值,将结果堆入数组中
            result[index] = value
            // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了
            if (++count === array.length) {
                // 将执行结果返回
                resolve(result)
            }
        }
        // 遍历穿入的数组,每个都执行then方法,获取到最终的结果
        array.forEach((p, index) => {
            // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中
            if (p instanceof MyPromise) {
                p.then(
                    // 成功时将结果直接加入数组中
                    value => {
                        addResult(result, index, value, resolve)
                    },
                    // 如果失败直接返回失败原因
                    reason => {
                        reject(reason)
                    }
                )
            }
            else {
                addResult(result, index, p, resolve)
            }
        })
    })
}

Promise.resolve方法的实现

关于Promise.resolve()方法的用法可以参考Promise.resolve()Promise.reject()

我们实现的思路主要如下:

  • 该方法是一个静态方法
  • 该方法接受的如果是一个值就将该值包装为一个MyPromise的实例对象返回,如果是一个MyPromise的实例对象,调用then方法返回。

实现代码如下:

static resolve (value) {
    // 如果是MyPromise的实例,就直接返回这个实例
    if (value instanceof MyPromise) return value
    // 如果不是的话创建一个MyPromise实例,并返回传递的值
    return new MyPromise((resolve) => {
        resolve(value)
    })
}

finally方法的实现

关于finally方法可参考finally()实现该方法的实现代码如下:

finally (callback) {
    // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback
    // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
    // 我们需要在回调之后拿到成功的回调,所以需要把value也return
    // 失败的回调也抛出原因
    // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve
    return this.then(value => {
        // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value
        return MyPromise.resolve(callback()).then(() => value)
    }, reason => {
        // 失败之后调用的then方法,然后把失败的原因返回出去。
        return MyPromise.resolve(callback()).then(() => { throw reason })
    })
}

测试:

function p1 () {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve('p1')
        }, 2000)
    })
}
function p2 () {
    return new MyPromise((resolve, reject) => {
        reject('p2 reject')
    })
}
p2().finally(
    () => {
        console.log('finally p2')
        return p1()
    }
).then(
    value => {
        console.log(value)
    }, reason => {
        console.log(reason)
    }
)
// finally p2
// 两秒之后执行p2 reject

catch方法的实现

关于catch方法可以参考catch(),实现该方法其实非常简单,只需要在内部调用then方法,不传递第一个回调函数即可,

实现代码如下:

catch (callback) {
    return this.then(null, failCallback)
}

测试如下:

function p () {
    return new MyPromise((resolve, reject) => {
        reject(new Error('reject'))
    })
}
p()
    .then(value => {
        console.log(value)
    })
    .catch(reason => console.log(reason))

完整代码

// MyPromise.js
// 定义所有状态的常量
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
// Promise实质上就是一个类,首先创建一个Promise的类
class MyPromise {
    // 实例化Promise时需要一个回调函数,该回调函数立即执行
    constructor(executor) {
        try {
            // 在调用executor需要传递两个回调函数,分别是resolve和reject
            executor(this.resolve, this.reject)
        } catch (e) {
            // 发生异常调用reject
            this.reject(e)
        }
    }
    // Promise 的状态
    status = PENDING
    // 记录成功与失败的值
    value = undefined
    reason = undefined
    // 存储成功与失败的处理逻辑
    onFulfilled = []
    onRejected = []

    resolve = (value) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
        // 形参value表示,调用resolve时传递的参数
        // 如果当前状态不是pending,就直接跳出该逻辑
        if (this.status !== PENDING) return
        // 将状态修改为成功
        this.status = FULFILLED
        // 将传入的值进行保存
        this.value = value
        // 如果将状态修复为成功,调用成功的回调
        // this.onFulfilled && this.onFulfilled(this.value)
        while (this.onFulfilled.length) {
            // Array.prototype.shift() 用于删除数组第一个元素,并返回
            this.onFulfilled.shift()(this.value)
        }
    }
    reject = (reason) => {// 这里使用箭头函数是为了使其内部的this指向为该实例化后的对象
        // 形参reason表示,调用reject时传递的失败的原因
        // 如果当前状态不是pending,就直接跳出该逻辑
        if (this.status !== PENDING) return

        // 将状态修改为失败
        this.status = REJECTED

        // 保存失败的原因
        this.reason = reason

        // 如果将状态修复为失败,调用失败的回调
        // this.onRejected && this.onRejected(this.reason)
        while (this.onRejected.length) {
            // Array.prototype.shift() 用于删除数组第一个元素,并返回
            this.onRejected.shift()(this.reason)
        }
    }
    // then方法的实现
    then (onFulfilled, onRejected) {
        // 如果传递函数,就是用传递的函数,否则指定一个默认值,用于参数传递
        onFulfilled = onFulfilled ? onFulfilled : value => value
        // 同理
        onRejected = onRejected ? onRejected : reason => { throw reason }
        // then 方法返回一个MyPromise实例
        const promise = new MyPromise((resolve, reject) => {
            // 判断当前状态,根据状态调用指定回调
            if (this.status === FULFILLED) {
                // 这里并不需要延迟执行,而是通过setTimeout将其变成异步函数
                // 如果不变成异步的话是在函数内获取不到promise的
                setTimeout(() => {
                    try {
                        // 将成功的值作为参数返回
                        // 保存执行回调函数的结果
                        const result = onFulfilled(this.value)

                        // 如果返回的是一个普通的值,直接调用resolve
                        // 如果是一个MyPromise实例,根据返回的解决来决定是调用resolve,还是reject
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else if (this.status === REJECTED) {
                // 失败的处理同成功处理,只是调用的回调函数不同
                setTimeout(() => {
                    try {
                        const result = onRejected(this.reason)
                        resolvePromise(promise, result, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0)
            } else {
                // 表示既不是成功,也不是失败。这个时候保存传递进来的两个回调
                // this.onFulfilled.push(onFulfilled)
                // this.onRejected.push(onRejected)
                this.onFulfilled.push((value) => {
                    setTimeout(() => {
                        try {
                            const result = onFulfilled(value)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
                this.onRejected.push((reason) => {
                    setTimeout(() => {
                        try {
                            const result = onRejected(reason)
                            resolvePromise(promise, result, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0)
                })
            }
        })
        return promise
    }
    catch (callback) {
        return this.then(null, callback)
    }
    finally (callback) {
        // 如何拿到当前的promise的状态,使用then方法,而且不管怎样都返回callback
        // 而且then方法就是返回一个promise对象,那么我们直接返回then方法调用之后的结果即可
        // 我们需要在回调之后拿到成功的回调,所以需要把value也return
        // 失败的回调也抛出原因
        // 如果callback是一个异步的promise对象,我们还需要等待其执行完毕,所以需要用到静态方法resolve
        return this.then(value => {
            // 把callback调用之后返回的promise传递过去,并且执行promise,且在成功之后返回value
            return MyPromise.resolve(callback()).then(() => value)
        }, reason => {
            // 失败之后调用的then方法,然后把失败的原因返回出去。
            return MyPromise.resolve(callback()).then(() => { throw reason })
        })
    }
    /**
     * @description: 将多个Promise实例合并为一个Promise实例
     * @param {*} array Promise或者普通值
     * @returns {Promise}
     */
    static all (array) {
        // 用于存放最终结果的数组
        let result = []
        // 用于计算当前已经执行完的实例的数量
        let count = 0
        // 最后返回的是一个Promise实例
        return new MyPromise((resolve, reject) => {
            /**
             * @description: 将执行完毕的值加入结果数组,并根据实际情况决定是否调用resolve
             * @param {*} result 存放结果的数组
             * @param {*} index 要加入的索引
             * @param {*} value 要加入数组的值
             * @param {*} resolve Promise中的resolve
             */
            function addResult (result, index, value, resolve) {
                // 根据索引值,将结果堆入数组中
                result[index] = value
                // 执行完毕一个 count+1,如果当前值等于总长度的话说明已经执行结束了,可以直接调用resolve,说明已经成功执行完毕了
                if (++count === array.length) {
                    // 将执行结果返回
                    resolve(result)
                }
            }
            // 遍历穿入的数组,每个都执行then方法,获取到最终的结果
            array.forEach((p, index) => {
                // 判断p是不是MyPromise的实例,如果是的话调用then方法,不是直接将值加入数组中
                if (p instanceof MyPromise) {
                    p.then(
                        // 成功时将结果直接加入数组中
                        value => {
                            addResult(result, index, value, resolve)
                        },
                        // 如果失败直接返回失败原因
                        reason => {
                            reject(reason)
                        }
                    )
                }
                else {
                    addResult(result, index, p, resolve)
                }
            })
        })
    }
    static resolve (value) {
        // 如果是MyPromise的实例,就直接返回这个实例
        if (value instanceof MyPromise) return value
        // 如果不是的话创建一个MyPromise实例,并返回传递的值
        return new MyPromise((resolve) => {
            resolve(value)
        })
    }

}
function resolvePromise (promise, result, resolve, reject) {
    // 这里修改一下该函数,如果return的Promise实例对象,也就是传入的promise===result的话,说明在promise中return的是当前promise对象。
    if (promise === result) {
        // 这里调用reject,并抛出一个Error
        // return 是必须的,阻止程序向下执行
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 判断传递的result是不是MyPromise的实例对象
    if (result instanceof MyPromise) {
        // 说明是MyPromise的实例对象
        // 调用.then方法,然后在回调函数中获取到具体的值,然后调用具体的回调
        // result.then(value => resolve(value), reason => reject(reason))
        // 简写
        result.then(resolve, reject)
    } else {
        resolve(result)
    }
}
module.exports = MyPromise

到此这篇关于万字详解JavaScript手写一个Promise的文章就介绍到这了,更多相关JS手写Promise内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何从零开始利用js手写一个Promise库详解

    前言 ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现.ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言. 概念 ES6 原生提供了 Promise 对象. 所谓 Promise,就是一个对象,用来传递异步操作的消息.它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理. 三道思考题 刚开始写前端的时候,处理异步请求经常用

  • JS手写一个自定义Promise操作示例

    本文实例讲述了JS手写一个自定义Promise操作.分享给大家供大家参考,具体如下: 经常在面试题中会看到,让你实现一个Promsie,或者问你实现Promise的原理,所以今天就尝试利用class类的形式来实现一个Promise 为了不与原生的Promise命名冲突,这里就简单命名为MyPromise. class MyPromise { constructor(executor) { let _this = this this.state = 'pending' // 当前状态 this.v

  • JavaScript手写Promise核心原理

    目录 准备 完善 resolve/reject then 异步处理 链式调用 边界处理 catch 优化后完整代码 准备 首先,promise 有三种状态:pending fulfilled rejected; promise在实例化操作中, 有两个改变状态的方法,分别为resolve,reject; promise有很多方法,详情请见 mdn, 本篇文章先实现 promise的核心api: then和catch; 我们使用 es6 提供的 class 来实现 class MyPromise {

  • 万字详解JavaScript手写一个Promise

    目录 前言 Promise核心原理实现 Promise的使用分析 MyPromise的实现 在Promise中加入异步操作 实现then方法的多次调用 实现then的链式调用 then方法链式调用识别Promise对象自返回 捕获错误及 then 链式调用其他状态代码补充 捕获执行器错误 捕获then中的报错 错误与异步状态的链式调用 将then方法的参数变成可选参数 Promise.all方法的实现 Promise.resolve方法的实现 finally方法的实现 catch方法的实现 完整

  • JS前端面试题详解之手写bind

    目录 bind 的用法 this 的指向问题 积累参数 实现一个 bind 结尾 大家好,我是前端西瓜哥,今天我们用 JS 来实现内置的 bind 方法. bind 的用法 在实现之前,我们先学习一下 Function.prototype.bind 的用法. function.bind(thisArg[, arg1[, arg2[, ...]]]) bind 是函数特有的一个方法,可以创建一个绑定了 this 的新函数. 接受的参数为如下. 第 1 个参数 thisArg:用于修改 this 指

  • 详解使用Python写一个向数据库填充数据的小工具(推荐)

    一. 背景 公司又要做一个新项目,是一个合作型项目,我们公司出web展示服务,合作伙伴线下提供展示数据. 而且本次项目是数据统计展示为主要功能,并没有研发对应的数据接入接口,所有展示数据源均来自数据库查询, 所以验证数据没有别的入口,只能通过在数据库写入数据来进行验证. 二. 工具 Python+mysql 三.前期准备 前置:当然是要先准备好测试方案和测试用例,在准备好这些后才能目标明确将要开发自动化小工具都要有哪些功能,避免走弯路 3.1 跟开发沟通 1)确认数据库连接方式,库名 : 2)测

  • 详解JavaScript如何创建一个非自动播放的GIF网络组件

    目录 一些很可爱的测试数据 构建Web组件 逻辑 结果 今天,我将向您展示如何创建一个允许您的用户决定是否要播放 gif 的 Web 组件!让我们开始吧. 一些很可爱的测试数据 这里用的gif是小骆驼和猫的这种可爱互动: 哇,太可爱了!我可以看一天这个 构建 Web 组件 对于这个 Web 组件,我们需要一些东西: 画布(“缩略图”所在的位置) 一张图片(实际的 gif) 标有“gif”的标签 一些造型 让我们这样做: const noAutoplayGifTemplate = document

  • 详解用python写一个抽奖程序

    第一次使用python写程序,确实比C/C++之类方便许多.既然这个抽奖的数据不大,对效率要求并不高,所以采用python写,更加简洁.清晰.方便. 1.用到的模块 生成随机数的模块random 用来读取excel表格的模块xlrd 2.思路:首先打开excel表格,然后读取其中某个单元格或者某行或某列的元素,进行输出或存储. 3.如何保证随机:随机的关键在于取随机数.每抽一个人之前,我们随机生成一个随机数i,i代表了读取第i个人的数据,由于i的生成是完全随机的,所以也就保证了选取的人员是完全随

  • 详解PyTorch手写数字识别(MNIST数据集)

    MNIST 手写数字识别是一个比较简单的入门项目,相当于深度学习中的 Hello World,可以让我们快速了解构建神经网络的大致过程.虽然网上的案例比较多,但还是要自己实现一遍.代码采用 PyTorch 1.0 编写并运行. 导入相关库 import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from torchvision import datasets, t

  • 详解用Kotlin写一个基于Spring Boot的RESTful服务

    Spring太复杂了,配置这个东西简直就是浪费生命.尤其在没有什么并发压力,随便搞一个RESTful服务,让整个业务跑起来先的情况下,更是么有必要纠结在一堆的XML配置上.显然这么想的人是很多的,于是就有了Spring Boot.又由于Java 8太墨迹于是有了Kotlin. 数据源使用MySql.通过Spring Boot这个基本不怎么配置的,不怎么微的微框架的Spring Data JPA和Hibernate来访问数据. 处理依赖 这里使用Gradle来处理依赖. 首先下载官网给的初始项目:

  • 「中高级前端面试」JavaScript手写代码无敌秘籍(推荐)

    1. 实现一个new操作符 new操作符做了这些事: 它创建了一个全新的对象. 它会被执行[[Prototype]](也就是__proto__)链接. 它使this指向新创建的对象.. 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用将返回该对象引用. function New(func) { va

  • 面试手写实现Promise.all

    目录 前言 常见面试手写系列 Promise.resolve 简要回顾 源码实现 Promise.reject 简要回顾 源码实现 Promise.all 简要回顾 源码实现 Promise.allSettled 简要回顾 源码实现 Promise.race 简单回顾 源码实现 结尾 前言 (ಥ﹏ಥ)曾经真实发生在一个朋友身上的真实事件,面试官让他手写一个Promise.all,朋友现场发挥不太好,没有写出来,事后他追问面试官给的模糊评价是基础不够扎实,原理性知识掌握较少... 当然整场面试失利

随机推荐