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

ops/2024/9/22 22:47:30/

前言

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

 🍅 个人主页:南木元元

在之前的文章中我们已经了解了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){// 进入会立即执行executorexecutor() }
}

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

javascript">class MyPromise {// 构造方法,executor是一个执行器,即为传进来的函数 (resolve, reject) => {}constructor(executor) {// 初始化值this.result = null; // 终值this.state = "pending"; // 状态// 绑定thisthis.resolve = this.resolve.bind(this);this.reject = this.reject.bind(this);// 执行传进来的函数executor(this.resolve, this.reject)}resolve(value) {// 如果执行resolve,状态变为fulfilledthis.state = "fulfilled"// 终值为传进来的值this.result = value}reject(reason) {// 如果执行reject,状态变为rejectedthis.state = "rejected"// 终值为传进来的reasonthis.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,状态变为fulfilledthis.state = "fulfilled";// 终值为传进来的值this.result = value;}
}reject(reason) {// state是不可变的,只有状态是等待,才执行状态修改if (this.state === "pending") {// 如果执行reject,状态变为rejectedthis.state = "rejected";// 终值为传进来的reasonthis.result = reason;}
}

再来测试下:

3.throw

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

javascript">try {// 执行传进来的函数executor(this.resolve, this.reject);
} catch (error) {// 捕捉到错误直接执行rejectthis.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 => valonRejected = 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,状态变为fulfilledthis.state = "fulfilled";// 终值为传进来的值this.result = value;// 执行保存的成功回调(如果Promise中有异步逻辑的情况)while (this.onFulfilledCallbacks.length) {// 删除并返回数组中第一个回调函数,然后()调用this.onFulfilledCallbacks.shift()(this.result);}}
}reject(reason) {// state是不可变的,只有状态是等待,才执行状态修改if (this.state === "pending") {// 如果执行reject,状态变为rejectedthis.state = "rejected";// 终值为传进来的reasonthis.result = reason;// 执行保存的失败回调(如果Promise中有异步逻辑的情况)while (this.onRejectedCallbacks.length) {this.onRejectedCallbacks.shift()(this.result);}}
}// 接收两个回调 onFulfilled, onRejected
then(onFulfilled, onRejected) {// 参数校验,确保一定是函数onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = 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 => valonRejected = 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);}});// 返回这个包装的Promisereturn 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 = []; // 保存失败回调// 绑定thisthis.resolve = this.resolve.bind(this);this.reject = this.reject.bind(this);try {// 执行传进来的函数executor(this.resolve, this.reject);} catch (error) {// 捕捉到错误直接执行rejectthis.reject(error);}}resolve(value) {// state是不可变的,只有状态是等待,才执行状态修改if (this.state === "pending") {// 如果执行resolve,状态变为fulfilledthis.state = "fulfilled";// 终值为传进来的值this.result = value;// 执行保存的成功回调while (this.onFulfilledCallbacks.length) {// 删除并返回数组中第一个回调函数,然后()调用this.onFulfilledCallbacks.shift()(this.result);}}}reject(reason) {// state是不可变的,只有状态是等待,才执行状态修改if (this.state === "pending") {// 如果执行reject,状态变为rejectedthis.state = "rejected";// 终值为传进来的reasonthis.result = reason;// 执行保存的失败回调while (this.onRejectedCallbacks.length) {this.onRejectedCallbacks.shift()(this.result);}}}// 接收两个回调 onFulfilled, onRejectedthen(onFulfilled, onRejected) {// 参数校验,确保一定是函数onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => valonRejected = 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);}});// 返回这个包装的Promisereturn thenPromise;}
}

结语

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


http://www.ppmy.cn/ops/104092.html

相关文章

机器学习数学公式推导之高斯分布

文章目录 1、介绍引入1.1 频率派的观点1.2 贝叶斯派的观点1.3 小结 2、数学基础2.1 二阶中心矩2.2 样本方差2.3 高斯分布2.3.1 一维情况 MLE2.3.2 多维情况 本文参考 B站UP: shuhuai008 跳转 🌹🌹 1、介绍引入 在统计学和概率论中, P ( x ∣ …

ElementPlus实现页面,上部分是表单,下部分是表格

效果 <template><el-dialog v-model"produceDialogFormVisible" draggable custom-class"dialog-title" :title"title" :close-on-click-modal"false"><el-form label-width"120px"><el-row :gutter&q…

笔记 12 : 彭老师课本第 6 章, PWM ,代码实践

&#xff08;85&#xff09; 驱动写多了&#xff0c;会发现&#xff0c;首先就是硬件的初始化&#xff0c;配置硬件。 &#xff08;86&#xff09;查看源代码组织&#xff1a; &#xff08;87&#xff09; 编译过程不变&#xff1a; &#xff08;88&#xff09; 运行同以前的步…

Datawhale X 李宏毅苹果书 AI夏令营(深度学习进阶)task3

批量归一化 其实归一化简单一点理解就类似于我们学过的数学中的每个数值减去平均值除以标准差。 神经网络中的批量归一化&#xff08;Batch Normalization&#xff0c;BN&#xff09;就是其中一个“把山铲平”的想法。不要小看优化这个问题&#xff0c;有时候就算误差表面是凸…

大数据-106 Spark Graph X 计算学习 案例:1图的基本计算、2连通图算法、3寻找相同的用户

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Local GAP - Financial Statement Version 【海外BS\PL报表】

业务场景&#xff1a; 基于海外IFRS等会计准则为客户定义一套BS\PL报表 BS - 从科目余额抓取 PL - 从利润中心报表抓取 会计报表版本的建立&#xff1a; 路径&#xff1a;IMG>财务会计&#xff08;新&#xff09;>总账会计核算&#xff08;新&#xff09;主数据>总…

JUC-指令有序性

指令重排 JVM 会在不影响正确性的前提下&#xff0c;可以调整语句的执行顺序&#xff0c;思考下面一段代码 static int i; static int j; // 在某个线程内执行如下赋值操作 i ...; j ...; 可以看到&#xff0c;至于是先执行 i 还是 先执行 j &#xff0c;对最终的结果不…

租用服务器都有哪些用途?

大部分的企业选择开展网络线上业务后&#xff0c;为了方便网络运行的更加顺畅&#xff0c;通常会选择租用或者托管服务器&#xff0c;那企业选择租用服务器都有哪些用途呢&#xff1f; 企业选择服务器租用可以进行网站托管服务&#xff0c;将个人或者是企业的网站部署在服务器上…