Promise内幕揭秘:手写实现背后的奥秘

前言

📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!

🍅 个人主页:************************************************************南木元元****************************************************************

之前的文章中我们已经了解了Promise的基本用法,今天我们就来手写实现一下Promise。


目录

resolve和reject方法

1.实现resolve和reject

2.状态不可变

3.throw

then方法

1.实现then

2.定时器情况

3.链式调用

4.微任务

结语


resolve和reject方法

Promise其实就是一个容器,里面保存异步操作的结果。来看一段Promise的代码:

javascript 复制代码
let p1 = new Promise((resolve, reject) => {
    resolve('成功')
    reject('失败')
})
console.log('p1', p1)

let p2 = new Promise((resolve, reject) => {
    reject('失败')
    resolve('成功')
})
console.log('p2', p2)

let p3 = new Promise((resolve, reject) => {
    throw('报错')
})
console.log('p3', p3)

在浏览器输出一下看看:

我们来分析一下其中的原理:

1.Promise 是一个类,在执行这个类的时候会传入一个执行器,这个执行器会立即执行。

2.执行了resolve,Promise状态会变成fulfilled。

3.执行了reject,Promise状态会变成rejected。

4.Promise只以第一次为准,状态一经改变,就无法再被改变了。

5.Promise中有throw的话,就相当于执行了reject。

下面就来一一实现。

1.实现resolve和reject

新建一个 MyPromise 类,传入执行器 executor。

javascript 复制代码
// 新建 MyPromise 类
class MyPromise {
  // 构造方法,executor是一个执行器,即为传进来的函数 (resolve, reject) => {}
  constructor(executor){
    // 进入会立即执行executor
    executor() 
  }
}

初始化,接收传入的 resolve 和 reject 方法。其中有很重要的一步是绑定this,这是为了让resolve和reject的this指向永远指向当前的MyPromise实例,防止随着函数执行环境的改变而改变。

javascript 复制代码
class MyPromise {
    // 构造方法,executor是一个执行器,即为传进来的函数 (resolve, reject) => {}
    constructor(executor) {
        // 初始化值
        this.result = null; // 终值
        this.state = "pending"; // 状态
        // 绑定this
        this.resolve = this.resolve.bind(this);
        this.reject = this.reject.bind(this);
        // 执行传进来的函数
        executor(this.resolve, this.reject)
    }

    resolve(value) {
        // 如果执行resolve,状态变为fulfilled
        this.state = "fulfilled"
        // 终值为传进来的值
        this.result = value
    }

    reject(reason) {
        // 如果执行reject,状态变为rejected
        this.state = "rejected"
        // 终值为传进来的reason
        this.result = reason
    }
}

现在来测试一下代码:

javascript 复制代码
const test1 = new MyPromise((resolve, reject) => {
    resolve('成功')
})
console.log(test1) 

const test2 = new MyPromise((resolve, reject) => {
    reject('失败')
})
console.log(test2) 

2.状态不可变

上面的代码是有问题的。

javascript 复制代码
const test1 = new MyPromise((resolve, reject) => {
    resolve('成功')
    reject('失败')
})
console.log(test1) 

正确的应该是fulfilled成功状态,这里明显没有以第一次为准,状态没有凝固。

解决起来很简单,只需加个条件判断:

javascript 复制代码
resolve(value) {
    // state是不可变的,只有状态是等待,才执行状态修改
    if (this.state === "pending") {
        // 如果执行resolve,状态变为fulfilled
        this.state = "fulfilled";
        // 终值为传进来的值
        this.result = value;
    }
}

reject(reason) {
    // state是不可变的,只有状态是等待,才执行状态修改
    if (this.state === "pending") {
        // 如果执行reject,状态变为rejected
        this.state = "rejected";
        // 终值为传进来的reason
        this.result = reason;
    }
}

再来测试下:

3.throw

Promise中有throw的话,就相当于执行了reject。使用try...catch来捕获错误:

javascript 复制代码
try {
    // 执行传进来的函数
    executor(this.resolve, this.reject);
} catch (error) {
    // 捕捉到错误直接执行reject
    this.reject(error);
}

来测试下:

then方法

then 方法接收两个回调,一个是成功回调,一个是失败回调,内部做的事情就是进行状态判断:

  • 如果Promise状态是fulfilled则执行成功回调
  • 如果Promise状态是rejected则执行失败回调
javascript 复制代码
// 马上输出 "成功"
const p1 = new Promise((resolve, reject) => {
    resolve('成功')
}).then(res => console.log(res), err => console.log(err))

下面就来一步一步地实现then方法。

1.实现then

javascript 复制代码
// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {
    // 参数校验,确保一定是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    if (this.state === 'fulfilled') {
        // 如果当前为成功状态,执行第一个回调
        onFulfilled(this.result)
    } else if (this.state === 'rejected') {
        // 如果当前为失败状态,执行第二哥回调
        onRejected(this.result)
    }
}

来测试下:

javascript 复制代码
const test = new MyPromise((resolve, reject) => {
    resolve('成功')
}).then(res => console.log(res), err => console.log(err))

2.定时器情况

如resolve或reject在定时器里,则需要定时器结束后再执行then。

javascript 复制代码
// 1秒后输出 "失败"
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('失败')
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

上面代码中,怎么才能保证,1秒后才执行then里的失败回调呢?答案是使用数组来保存回调。

为什么使用数组呢?因为一个promise实例可能会多次then。如下:

javascript 复制代码
promise.then(value => {
  console.log(1)
  console.log('resolve', value)
})
 
promise.then(value => {
  console.log(2)
  console.log('resolve', value)
})

promise.then(value => {
  console.log(3)
  console.log('resolve', value)
})

使用数组就可以一个一个保存下来,后续再去一个个执行。

那如何判断定时器是否执行完毕呢?很简单,只要状态是pending,就证明定时器还没跑完。等定时器结束,执行了resolve或者reject,再去判断要去执行哪个数组中的回调函数。

实现代码:

javascript 复制代码
constructor(executor) {
    // 初始化值
    this.result = null; // 终值
    this.state = "pending"; // 状态
    // Promise中有异步逻辑的情况,执行then还是pending状态,暂时保存两个回调
    this.onFulfilledCallbacks = []; // 保存成功回调
    this.onRejectedCallbacks = []; // 保存失败回调
    ...
}

resolve(value) {
    // state是不可变的,只有状态是等待,才执行状态修改
    if (this.state === "pending") {
        // 如果执行resolve,状态变为fulfilled
        this.state = "fulfilled";
        // 终值为传进来的值
        this.result = value;
        // 执行保存的成功回调(如果Promise中有异步逻辑的情况)
        while (this.onFulfilledCallbacks.length) {
            // 删除并返回数组中第一个回调函数,然后()调用
            this.onFulfilledCallbacks.shift()(this.result);
        }
    }
}

reject(reason) {
    // state是不可变的,只有状态是等待,才执行状态修改
    if (this.state === "pending") {
        // 如果执行reject,状态变为rejected
        this.state = "rejected";
        // 终值为传进来的reason
        this.result = reason;
        // 执行保存的失败回调(如果Promise中有异步逻辑的情况)
        while (this.onRejectedCallbacks.length) {
            this.onRejectedCallbacks.shift()(this.result);
        }
    }
}

// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {
    // 参数校验,确保一定是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    if (this.state === 'fulfilled') {
        // 如果当前为成功状态,执行第一个回调
        onFulfilled(this.result)
    } else if (this.state === 'rejected') {
        // 如果当前为失败状态,执行第二哥回调
        onRejected(this.result)
    } else if (this.state === "pending") {
        // 如果当前状态为待定状态(即执行then方法的时候还是pending状态,就代表Promise中有异步逻辑),暂时保存两个回调
        // 等到异步操作结束,执行成功或失败函数的时候再执行回调
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
    }
}

来测试下:

javascript 复制代码
const test2 = new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功') // 1秒后输出 成功
        // resolve('成功') // 1秒后输出 失败
    }, 1000)
}).then(res => console.log(res), err => console.log(err))

3.链式调用

then支持链式调用,下一次then执行受上一次then返回值的影响。

javascript 复制代码
// 链式调用 输出 200
const p3 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => 2 * res, err => console.log(err))
    .then(res => console.log(res), err => console.log(err))

// 链式调用 输出300
const p4 = new Promise((resolve, reject) => {
    resolve(100)
}).then(res => new Promise((resolve, reject) => resolve(3 * res)), err => console.log(err))
    .then(res => console.log(res), err => console.log(err))

从上面示例中,我们可以总结出以下几个点:

1.then方法本身会返回一个新的Promise对象。

2.如果返回值是promise对象,返回值为成功,新promise就是成功。

3.如果返回值是promise对象,返回值为失败,新promise就是失败。

4.如果返回值非promise对象,新promise对象就是成功,值为此返回值。

如何实现then完还能再then呢?答案是then执行后返回一个Promise对象,就能继续then。

代码实现:

javascript 复制代码
// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {
    // 参数校验,确保一定是函数
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
    //then支持链式调用,本身会返回一个新的Promise对象
    var thenPromise = new MyPromise((resolve, reject) => {
        //cb是成功或失败的回调函数
        const resolvePromise = (cb) => {
            try {
                //执行回调并保存结果
                const x = cb(this.result);
                if (x === thenPromise) {
                    // then方法返回的不能是自身,避免循环引用
                    throw new Error("不能返回自身");
                }
                if (x instanceof MyPromise) {
                    // 如果回调函数的返回值是Promise
                    // 如果返回值是promise对象,返回值为成功,新promise就是成功
                    // 如果返回值是promise对象,返回值为失败,新promise就是失败
                    // 谁知道返回的promise是失败成功?只有then知道。
                    x.then(resolve, reject); 	//调用 then 方法,将其状态变为 fulfilled 或者 rejected
                } else {
                    // 非Promise就直接成功
                    resolve(x);
                }
            } catch (err) {
                reject(err);
            }
        };

        if (this.state === "fulfilled") {
            // 如果当前为成功状态,执行第一个回调(把成功回调传入resolvePromise统一处理)
            resolvePromise(onFulfilled);
        } else if (this.state === "rejected") {
            // 如果当前为失败状态,执行第二个回调(把失败回调传入resolvePromise统一处理)
            resolvePromise(onRejected);
        } else if (this.state === "pending") {
            // 如果当前状态为待定状态(即执行then方法的时候还是pending状态,就代表Promise中有异步逻辑),暂时保存两个回调
            // 等到异步操作结束,执行成功或失败函数的时候再执行回调
            this.onFulfilledCallbacks.push(onFulfilled);
            this.onRejectedCallbacks.push(onRejected);
        }
    });

    // 返回这个包装的Promise
    return thenPromise;
}

来测试下:

javascript 复制代码
const test3 = new Promise((resolve, reject) => {
    resolve(100) // 输出 状态:成功 值: 200
    // reject(100) // 输出 状态:成功 值:300
}).then(res => 2 * res, err => 3 * err)
  .then(res => console.log('成功', res), err => console.log('失败', err))

const test4 = new Promise((resolve, reject) => {
    resolve(100) // 输出 状态:失败 值:200
    // reject(100) // 输出 状态:成功 值:300
}).then(res => new Promise((resolve, reject) => reject(2 * res)), err => new Promise((resolve, reject) => resolve(3 * err)))
  .then(res => console.log('成功', res), err => console.log('失败', err))

4.微任务

我们知道,then方法是一个微任务(如果不了解宏任务和微任务,可以去看这篇文章),这里我们使用定时器来让resolvePromise函数异步执行。

实现代码:

javascript 复制代码
//cb是成功或失败的回调函数
const resolvePromise = (cb) => {
    //因为then是个微任务,这里使用定时器来模拟,让resolvePromise异步执行就行
    setTimeout(() => {
        try {
            //执行回调并保存结果
            const x = cb(this.result);
            if (x === thenPromise) {
                // then方法返回的不能是自身,避免循环引用
                throw new Error("不能返回自身");
            }
            if (x instanceof MyPromise) {
                // 如果回调函数的返回值是Promise
                // 如果返回值是promise对象,返回值为成功,新promise就是成功
                // 如果返回值是promise对象,返回值为失败,新promise就是失败
                // 谁知道返回的promise是失败成功?只有then知道。
                x.then(resolve, reject); 	//调用 then 方法,将其状态变为 fulfilled 或者 rejected
            } else {
                // 非Promise就直接成功
                resolve(x);
            }
        } catch (err) {
            reject(err);
        }
    });
};

来测试下:

javascript 复制代码
const test4 = new MyPromise((resolve, reject) => {
    resolve(1)
}).then(res => console.log(res), err => console.log(err))

console.log(2)

好了,到这里,Promise就基本实现了。最后附上完整代码:

javascript 复制代码
class MyPromise {
    constructor(executor) {
        // 初始化值
        this.result = null; // 终值
        this.state = "pending"; // 状态
        // Promise中有异步逻辑的情况,执行then还是pending状态,暂时保存两个回调
        this.onFulfilledCallbacks = []; // 保存成功回调
        this.onRejectedCallbacks = []; // 保存失败回调
        // 绑定this
        this.resolve = this.resolve.bind(this);
        this.reject = this.reject.bind(this);
        try {
            // 执行传进来的函数
            executor(this.resolve, this.reject);
        } catch (error) {
            // 捕捉到错误直接执行reject
            this.reject(error);
        }
    }

    resolve(value) {
        // state是不可变的,只有状态是等待,才执行状态修改
        if (this.state === "pending") {
            // 如果执行resolve,状态变为fulfilled
            this.state = "fulfilled";
            // 终值为传进来的值
            this.result = value;
            // 执行保存的成功回调
            while (this.onFulfilledCallbacks.length) {
                // 删除并返回数组中第一个回调函数,然后()调用
                this.onFulfilledCallbacks.shift()(this.result);
            }
        }
    }

    reject(reason) {
        // state是不可变的,只有状态是等待,才执行状态修改
        if (this.state === "pending") {
            // 如果执行reject,状态变为rejected
            this.state = "rejected";
            // 终值为传进来的reason
            this.result = reason;
            // 执行保存的失败回调
            while (this.onRejectedCallbacks.length) {
                this.onRejectedCallbacks.shift()(this.result);
            }
        }
    }

    // 接收两个回调 onFulfilled, onRejected
    then(onFulfilled, onRejected) {
        // 参数校验,确保一定是函数
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        //then支持链式调用,本身会返回一个新的Promise对象
        var thenPromise = new MyPromise((resolve, reject) => {
            //cb是成功或失败的回调函数
            const resolvePromise = (cb) => {
                //因为then是个微任务,这里使用定时器来模拟,让resolvePromise异步执行就行
                setTimeout(() => {
                    try {
                        //执行回调并保存结果
                        const x = cb(this.result);
                        if (x === thenPromise) {
                            // then方法返回的不能是自身,避免循环引用
                            throw new Error("不能返回自身");
                        }
                        if (x instanceof MyPromise) {
                            // 如果回调函数的返回值是Promise
                            // 如果返回值是promise对象,返回值为成功,新promise就是成功
                            // 如果返回值是promise对象,返回值为失败,新promise就是失败
                            // 谁知道返回的promise是失败成功?只有then知道。
                            x.then(resolve, reject); 	//调用 then 方法,将其状态变为 fulfilled 或者 rejected
                        } else {
                            // 非Promise就直接成功
                            resolve(x);
                        }
                    } catch (err) {
                        reject(err);
                    }
                });
            };

            if (this.state === "fulfilled") {
                // 如果当前为成功状态,执行第一个回调(把成功回调传入resolvePromise统一处理)
                resolvePromise(onFulfilled);
            } else if (this.state === "rejected") {
                // 如果当前为失败状态,执行第二个回调(把失败回调传入resolvePromise统一处理)
                resolvePromise(onRejected);
            } else if (this.state === "pending") {
                // 如果当前状态为待定状态(即执行then方法的时候还是pending状态,就代表Promise中有异步逻辑),暂时保存两个回调
                // 等到异步操作结束,执行成功或失败函数的时候再执行回调
                this.onFulfilledCallbacks.push(onFulfilled);
                this.onRejectedCallbacks.push(onRejected);
            }
        });

        // 返回这个包装的Promise
        return thenPromise;
    }
}

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~

相关推荐
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
black^sugar6 小时前
纯前端实现更新检测
开发语言·前端·javascript
2401_857600957 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600957 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL7 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
小白学大数据7 小时前
如何使用Selenium处理JavaScript动态加载的内容?
大数据·javascript·爬虫·selenium·测试工具
2402_857583497 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake8 小时前
Vue3之性能优化
javascript·vue.js·性能优化