手写Promise教程_then方法
1、Promise结构分析
console.log(new Promise((resolve, reject) => {}));
通过打印Promise的实例对象,观察promise的对象结构:
分析上图,可以得出一些信息:
- 1、promise实例对象上有PromiseState属性和PromiseResult属性
- 2、Promise构造函数上有all方法、race方法、resolve方法、reject方法。
- 3、Promise构造函数的原型对象上有then方法、catch方法。
这些是Promise最核心的属性和方法,手写Promise也就是去实现它们的功能。
(资料图)
2、构造函数
创建一个promise_test.js文件,被在html文件中中引入:
index.html:
手写promise教程 <script src="./promise_test.js"></script> <script> new Promise((resolve, reject) => {}); </script>
promise_test.js:
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; executor(resolve, reject);}
在promise_test.js文件中写上一个Promise构造函数,把JS为我们提供的原生Promise构造函数覆盖掉,然后运行代码,报错:
Uncaught ReferenceError: resolve is not defined。
为什么会出现这个错误呢?
因为在index.html中new Promise( 匿名函数(resolve, reject) ),作为实参传进来的匿名函数(resolve, reject),在构造函数内调用的时候,传入了resolve和reject实参方法,但构造函数中没有这两个方法,因此我们要在构造函数中定义resolve和reject这两个方法:
2.1、resolve 和 reject
功能分析:
- 1、修改PromiseState的状态为fulfilled,并且PromiseState的状态只能修改一次。
- 2、可以接收一个参数,将这个参数的值赋给PromiseResult。
index.html:
new Promise((resolve, reject) => { resolve("test"); });
promise_test.js:
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; // 定义resolve函数 function resolve(data) { console.log(this.PromiseState); // undefined } executor(resolve, reject);}
既然要修改PromiseState的值,那么我们先尝试获取这个值,构造函数中声明的是pending,但结果却是:undefined,为什么?
this的指向问题,我们在代码中打印了了一下this,结果:Window,为什么是Window?
这里复习一下this的指向,1、普通函数中的this指向window。2、构造函数中的this指向实例对象。3、箭头函数中的this指向父级作用域的this。
ok,如果知道了这两点的话,那么就可以知道如何解决:1、将this的指向作为一个属性保存下来。2、使用箭头函数。
这里选择保存this的指向:
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; // 保存实例对象的this值 const _this = this; // 定义resolve函数 function resolve(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "fulfilled"; // resolved // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; } function reject(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "rejected"; // rejected // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; } executor(resolve, reject);}
index.html:
测试1:
const p = new Promise((resolve, reject) => { resolve("success");});console.log(p);
结果1:Promise{PromiseState: "fulfilled", PromiseResult: "success"}
——
测试2:
const p = new Promise((resolve, reject) => { reject("fail");});console.log(p);
结果2:Promise{PromiseState: "rejected", PromiseResult: "fail"}
——
经过测试,PromiseState和PromiseResult成功被修改!
3、then方法
3.1、功能分析
通过1的分析,我们可以得知then方法是放在原型对象上的,这样做的目的是为了通过实例对象调用.then进行链式调用。
链式调用是Promise最重要的特性,它解决了回调地狱的问题,下面我们来分析一下then方法的功能:
1、Promise.prototype.then方法:then(onResolved, onRejected) => {},
它传入两个回调函数作为形参,onResolved作为PromiseState为fulfilled时的回调函数,onRejected作为PromiseState为rejected时的回调函数。
验证代码:
const p = new Promise((resolve, reject) => { // resolve将PromiseState状态改为了fulfilled resolve("success"); // reject将PromiseState状态改为了rejected // reject("fail");});p.then(value => { // onResolved方法的回调函数,状态时fulfilled执行 console.log(value); // success}, reason => { // onRejected方法的回调函数,状态时rejected执行 console.log(reason); // fail});
2、then方法的返回值是一个新的promise对象,这个新的promise对象的结果状态由then指定的回调函数执行的返回值决定。
- 如果then回调函数的返回值是:非Promise的数据。那么新promise的状态(PromiseState)是fulfilled,值(PromiseResult)是这个非Promise的数据。
- 如果then回调函数的返回值是:Promise类型的数据,那么新的promise的状态(PromiseState)由返回值promise对象的状态决定,值(PromiseResult)也由它的值决定。
- 如果抛出异常,新promise变成rejected,reason为抛出的异常。
验证代码:
const p = new Promise((resolve, reject) => { // resolve将PromiseState状态改为了fulfilled resolve("success"); // reject将PromiseState状态改为了rejected // reject("fail");});const p2 = p.then(value => { // onResolved方法的回调函数,状态时fulfilled执行下面的代码 // 返回值是value变量,非promise,也就是"success" // return value; // 返回值是promise对象 return new Promise((resolve, reject) => { // 如果调用resolve,那么p2的PromiseStatus就是fulfilled,PromiseResult是success2 resolve("success2"); // 如果调用reject,那么p2的PromiseStatus就是rejected,PromiseResult是fail2 // reject("fail2"); // 如果抛出异常,那么p2的PromiseStatus就是rejected,PromiseResult是error // throw "error"; });}, reason => { // onRejected方法的回调函数,状态时rejected执行下面的代码 console.log(reason); // fail});console.log(p2);
3、链式调用,并且异步调用。
- 1、then里面的代码,会按照链式调用的顺序,顺序执行。
- 2、then里面的代码,是异步执行。
验证代码:
const p = new Promise((resolve, reject) => { resolve(); console.log("111");});const p2 = p.then(value => { // 没有return返回值,默认是return undefined,这个也是非promise的数据,因此状态是fulfilled,值是undefined console.log("222");}, reason => { console.log(reason); // fail});p2.then(value => { console.log("333");});console.log("444");
执行结果:111 444 222 333
可以看到,同步代码先执行,执行完之后再执行then中的异步代码,并且是按照链式操作的顺序执行下来。
以上就是then方法的功能分析,下面是手写实现:
3.2、功能实现
1、实现异步执行 + 判断返回值进行不同的操作
Promise.prototype.then = function(onResolved, onRejected) { // then函数的返回值是一个新的promise对象 return new Promise((resolve, reject) => { // 如果实例对象上的PromiseState是fulfilled,执行onResolved if (this.PromiseState === "fulfilled") { // 异步执行 setTimeout(() => { try { let result = onResolved(this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } }); } // 如果实例对象上的PromiseState是fulfilled,执行onRejected if (this.PromiseState === "rejected") { // 异步执行 setTimeout(() => { try { let result = onRejected(this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } }); } });}
上述代码看似可以完美实现,但是却存在一个缺陷,以下是测试index.html:
const p = new Promise((resolve, reject) => { // 如果这里是异步的,出现bug:永远不会触发then方法 setTimeout(() => { resolve("111"); }, 1000);});const p2 = p.then(value => { console.log("then中:" + value);}, reason => { console.log(reason);});
上述测试,及时是等一秒甚至一百秒,then方法中的回调也不会执行,为什么?
因为这个时候p这个对象中的promiseState还处于pending状态,所以我们要在then方法中加上第三种情况,pending。
做法是:将回调函数保存起来,在resolve和reject方法中去调用,如此就可以实现链式调用。
2、链式调用的实现
promise_test.js:
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; // 用于保存回调函数的数组 this.callbacks = []; // 保存实例对象的this值 const _this = this; // 定义resolve函数 function resolve(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "fulfilled"; // fulfilled // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,成功的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Resolved(data); }); }); } function reject(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "rejected"; // rejected // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,失败的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Rejected(data); }); }); } executor(resolve, reject);}Promise.prototype.then = function(onResolved, onRejected) { // then函数的返回值是一个新的promise对象 return new Promise((resolve, reject) => { // 如果实例对象上的PromiseState是fulfilled,执行onResolved if (this.PromiseState === "fulfilled") { // 异步执行 setTimeout(() => { try { let result = onResolved(this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } }); } // 如果实例对象上的PromiseState是rejected,执行onRejected if (this.PromiseState === "rejected") { // 异步执行 setTimeout(() => { try { let result = onRejected(this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } }); } // 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况 // 后续的新promise对象的状态都是pending,都会走这里。 // 我们就将回调函数保存起来,在resolve和reject方法中去调用 if (this.PromiseState === "pending") { // 注意保存一下this的指向,因为下面两个都是普通函数,内部的this指向是window const _this = this; function Resolved() { try { let result = onResolved(_this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } } function Rejected() { try { let result = onRejected(_this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } } this.callbacks.push({ Resolved, Rejected }); } });}
3、链式调用的实现_代码优化
可以看出,上述有很多重复代码,是为了更好帮助理解,故意的。
以下则是优化后的代码promise_test.js:
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; // 用于保存回调函数的数组 this.callbacks = []; // 保存实例对象的this值 const _this = this; // 定义resolve函数 function resolve(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "fulfilled"; // fulfilled // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,成功的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Resolved(data); }); }); } function reject(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "rejected"; // rejected // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,失败的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Rejected(data); }); }); } executor(resolve, reject);}Promise.prototype.then = function(onResolved, onRejected) { // 注意保存一下this的指向,因为下面普通函数,内部的this指向是window const _this = this; // then函数的返回值是一个新的promise对象 return new Promise((resolve, reject) => { function handler(func) { try { let result = func(_this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } } // 如果实例对象上的PromiseState是fulfilled,执行onResolved if (this.PromiseState === "fulfilled") { // 异步执行 setTimeout(() => { handler(onResolved); }); } // 如果实例对象上的PromiseState是rejected,执行onRejected if (this.PromiseState === "rejected") { // 异步执行 setTimeout(() => { handler(onRejected); }); } // 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况 // 后续的新promise对象的状态都是pending,都会走这里。 // 我们就将回调函数保存起来,在resolve和reject方法中去调用 if (this.PromiseState === "pending") { this.callbacks.push({ Resolved: () => { handler(onResolved); }, Rejected: () => { handler(onRejected); } }); } });}
测试代码index.html:
const p = new Promise((resolve, reject) => { // 开启异步 // setTimeout(() => { // resolve("ok"); reject("no"); // });});p.then(value => { console.log(111);}, reason => { // 如果返回值是非promise类型的数据 return reason;}).then(value => { console.log(value); // "no" // 如果返回值是promise类型的数据 return new Promise((resolve, reject) => { resolve(333); });}, reason => { // ...}).then(value => { console.log("收到的值是:", value); // 333 // 如果抛出异常 throw "error"}).then(() => { // ...}, reason => { console.log(reason); // error})// 如果then中是空值,什么也不传,出现bug!!!.then().then(() => { console.log("我的上一个then是空值,我还可以输出吗?");});
最后的测试,如果then中是空值,什么也不传,出现bug!!!
4、值的穿透
解决:
Promise.prototype.then = function(onResolved, onRejected) { // 注意保存一下this的指向,因为下面普通函数,内部的this指向是window const _this = this; // 判断回调函数参数 if (typeof onRejected !== "function") { onRejected = reason => { throw reason; } } if (typeof onResolved !== "function") { onResolved = value => value; } .....
当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这叫做值的穿透。
4、完整代码示例
写这个then方法,被这个then方法折磨了一整天了,中途踩了一些巨坑。
关于Promise的手写教程就先写到这里吧,catch、all、race、resolve、reject等方法的实现,会在下一篇博客中进行讲解。
这些方法相对then方法来说就简单得多了。
如果关于本篇文章有什么问题的话,可以私信我,谢谢阅读。
function Promise(executor) { // 添加属性 this.PromiseState = "pending"; this.PromiseResult = undefined; // 用于保存回调函数的数组 this.callbacks = []; // 保存实例对象的this值 const _this = this; // 定义resolve函数 function resolve(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "fulfilled"; // fulfilled // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,成功的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Resolved(data); }); }); } function reject(data) { // 判断状态,promise状态只能更改一次 if (_this.PromiseState !== "pending") return; // 1、修改对象的状态(promiseState) _this.PromiseState = "rejected"; // rejected // 2、设置对象的结果值(promiseResult) _this.PromiseResult = data; // 3、调用异步任务下,失败的回调函数 setTimeout(() => { _this.callbacks.forEach(item => { item.Rejected(data); }); }); } try { executor(resolve, reject); } catch (e) { reject(e); }}Promise.prototype.then = function(onResolved, onRejected) { // 注意保存一下this的指向,因为下面普通函数,内部的this指向是window const _this = this; // 判断回调函数参数 if (typeof onRejected !== "function") { onRejected = reason => { throw reason; } } if (typeof onResolved !== "function") { onResolved = value => value; } // then函数的返回值是一个新的promise对象 return new Promise((resolve, reject) => { function handler(func) { try { let result = func(_this.PromiseResult); // 如果返回值是promise对象 if (result instanceof Promise) { result.then(v => { resolve(v); }, r => { reject(r); }); } else { // 如果返回值是非promise对象 resolve(result); } } catch (e) { // 如果抛出异常,新promise变成rejected,reason为抛出的异常。 reject(e); } } // 如果实例对象上的PromiseState是fulfilled,执行onResolved if (this.PromiseState === "fulfilled") { // 异步执行 setTimeout(() => { handler(onResolved); }); } // 如果实例对象上的PromiseState是rejected,执行onRejected if (this.PromiseState === "rejected") { // 异步执行 setTimeout(() => { handler(onRejected); }); } // 如果new Promise中的函数是异步的,例如一秒之后再调用resolveo或者reject的情况 // 后续的新promise对象的状态都是pending,都会走这里。 // 我们就将回调函数保存起来,在resolve和reject方法中去调用 if (this.PromiseState === "pending") { this.callbacks.push({ Resolved: () => { handler(onResolved); }, Rejected: () => { handler(onRejected); } }); } });}
关键词:
-
手写Promise教程_then方法
手写Promise教程_then方法1、Promise结构分析console log(newPromise((resolve,reject)=>{}));通过打印Prom
-
全球快资讯:琼斯袖珍图书馆_关于琼斯袖珍图书馆介绍
琼斯袖珍图书馆,关于琼斯袖珍图书馆介绍这个很多人还不知道,我们一起来看看!1、《琼斯袖珍图书馆》是一款
-
脑洞大开,鬼灭之刃主题高达模型改造合集_天天速递
《鬼灭之刃》是当下比较受模友们喜欢的动漫,可以说其中的每个角色塑造得都有血有肉。因为动漫人气较高,所
-
三通道恒流256级灰度LED驱动lC-WH3803D|世界热点
随着led驱动技术的不断进步,常规led驱动调光电路一般结构简单,调光灰度低,调光界限明显,刷新率过低会造
-
施聚_关于施聚介绍 全球微头条
施聚,关于施聚介绍这个很多人还不知道,现在让我们一起来看看吧!1、施聚(1389年7月22日-1462年9月8日),
-
上海举行大中小学“劳模工匠进校园”推进会_当前视点
市教育系统关工委、市校外联办公室和市劳模协会工会管理学院劳模学员分会决定,把“百名劳模进校园”劳模工
-
汇嘉时代: 关于召开2022年度业绩说明会的公告 当前信息
汇嘉时代:关于召开2022年度业绩说明会的公告
-
冻疮怎么根治不复发土方法_冻疮怎么根治
1、治愈冻疮的方法是保暖。2、这种温暖不是你觉得自己是否足够温暖,而是你的身体反应是否足够。3、比如在
-
世界今亮点!五棱柱展开图与平面图_五棱柱展开图
你们好,最近小品发现有诸多的小伙伴们对于五棱柱展开图与平面图,五棱柱展开图这个问题都颇为感兴趣的,今
-
锡装股份股东户数增加2.04%,户均持股13.04万元_世界热头条
锡装股份最新股东户数9034户,低于行业平均水平。公司户均持有流通股份2214股;户均流通市值13 04万元。
-
库里:维金斯今晚的进攻积极性很出色 我们需要他每晚都这样打 天天时快讯
维金斯每晚都能贡献很好的防守,他投进了一些难度很高的球,就像你所说的,他今天为我们提供了篮板和在进攻
-
英威腾(002334)5月11日主力资金净买入552.12万元|环球今热点
截至2023年5月11日收盘,英威腾(002334)报收于11 16元,下跌0 62%,换手率2 09%,成交量14 72万手,成交额1 64亿元。
-
当前快看:亚马逊全球开店,品牌型卖家销售额在2022年实现双位数增长
据亚马逊官方最新数据显示,全球开店的品牌型卖家在2022年实现了双位数的销售额增长。这一成绩得益于品牌型
-
长塘瑶族乡开展农村集体“三资”管理突出问题专项整治行动
华声在线讯(通讯员肖贺)为深层次提高集体“三资”监管水平,按照省、市、县“三湘护农”专项行动部署要求,
-
最新消息:霍格沃茨遗产巨魔最大的敌人是卷心菜如果你知道这个把戏
一位霍格沃茨遗产玩家发现了一个技巧,可以让一束卷心菜变得足够结实,足以打倒整个巨魔。正如在Reddit上分
-
全球速讯:康强电子(002119):股价成功突破年线压力位-后市看多(涨)(05-11)
资金流向数据,主力资金净流出11 42万元,占总成交额0%,其中超大单净流出219 59万元,大单净流入208
-
eeekou2 tumblr com 环球视讯
今天小编肥嘟来为大家解答以上的问题。eeekou2tumblrcom相信很多小伙伴还不知道,现在让我们一起来看看吧!1
-
有关统治的电影《柏林陷落》:极端条件下人性的光辉还存在吗?|全球速讯
有关统治的电影《柏林陷落》:极端条件下人性的光辉还存在吗?,纳粹,电影,战争,希特勒,柏林陷落
-
国家移民管理局:进一步优化出入境管理政策措施
01:16国家移民管理局今天发布公告,自今年5月15日起,实施全面恢复实行内地居民赴港澳团队旅游签注“全国通
-
以“数”为笔,书写数字江苏为民服务新篇章-每日报道
欲穷千里目,更上一层楼。随着“数字中国”战略逐步推进,各地“数字政府”建设步伐也在快马加鞭的进行。近