首页>>前端>>JavaScript->代码拆解实现Promise及其周边(干货)

代码拆解实现Promise及其周边(干货)

时间:2023-11-29 本站 点击:0

Promise对象用于表示一个异步操作的最终状态(成功/失败)及其结果值。 —— MDN

Promise的出现使我们可以优雅地处理异步操作,脱离回调地狱的痛苦。 有一利必有一弊,它成为了面试必考问题之一 ,转化为另一种痛苦生活在我们身边... 当然,这是句玩笑话~ ?

上次看Promise大概一个多月前了,花了两天的空暇时间基于Promise/A+手写了一遍,大多云里雾里。为了巩固,趁着周末又写了一遍,彻底梳理清楚✌。本文主要还是拆解实现Promise丐版及其周边。

基本特点

新建Promise会立即执行,无法中断。

Promise有三种状态:pengdingfulfilledrejected。只有pengding -> fulfilledpending -> rejected两种状态流,且状态更改后,不可再次更改。

resolve为成功状态;reject为失败状态。

Promise基本使用?

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise复制代码

拆解实现Promise丐版

实现基本逻辑

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise

引入实现的丐版Promise,执行下上述 ?

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})

执行打印出success

处理异步情况

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})

执行无输出结果,打印then函数的this.status发现状态仍为pengding。 这是为什么呢? 知道事件循环的朋友们应该知道setTimeout是异步且属于宏任务,将会在下个事件循环宏任务中执行,而p.then为当前宏任务下的同步代码。因此,that.status仍为pengding,所以无输出结果。

既然这样,我们需要添加两个字段onFulfilledonRejected去缓存成功和失败的回调,在resolvereject时候再去执行缓存函数。

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullthis.onFulfilled=nullthis.onRejected=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value//有缓存成功回调,则执行that.onFulfilled&&that.onFulfilled(value)}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason//有缓存失败回调,则执行that.onRejected&&that.onRejected(value)}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}else{//挂载状态下缓存成功和失败回调this.onFulfilled=onFulfilledthis.onRejected=onRejected}}module.exports=Promise

改进后,执行上述异步情况代码,成功打印success

再在异步情况下加点料?,多次执行不同的p.then函数。

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})p.then((value)=>{console.log('第一次',value)},(reason)=>{console.log(reason)})p.then((value)=>{console.log('第二次',value)},(reason)=>{console.log(reason)})//第二次success

发现我们第一个p.then代码没有被执行。 来分析下原因:目前我们只用一个变量去存储成功和失败的回调,当我setTimeout执行之前,我的两个p.then已按顺序执行完毕。那么,第二个p.then就将覆盖第一个p.then所赋值的回调函数,所以执行结果为第二次 success

改进下代码,我们用数组的方式去存储所有的回调函数。

constPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=null//数组记录所有的成功回调this.onFulfilled=[]//数组记录所有的失败回调this.onRejected=[]functionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value//有缓存成功回调,则执行that.onFulfilled.forEach((fn)=>fn(value))}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason//有缓存失败回调,则执行that.REJECTED.forEach((fn)=>fn(reason))}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}else{//挂载状态下缓存所有的成功和失败回调this.onFulfilled.push(onFulfilled)this.onRejected.push(onRejected)}}module.exports=Promise

处理链式调用及值穿透(同步)

Promise的重中之重就是链式调用,主要处理的就是下述三种情况。根据Promise/A+的思想,每次执行完Promise.then就创建新的promise,并把上一个then的返回值传递给下个promisethen方法,就可达到链式调用及值穿透的效果。resolvereject同理。

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{//目前只处理同步链式调用resolve('success')})//返回普通值p.then((value)=>{returnvalue}).then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined//返回promisep.then((value)=>{returnnewPromise((resolve,reject)=>{resolve(value)})}).then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined//值穿透p.then().then((value)=>{console.log(value)})//报错Cannotreadproperty'then'ofundefined

改进代码主要部分如下:

...Promise.prototype.then=function(onFulfilled,onRejected){letthat=this//值穿透问题onFulfilled=typeofonFulfilled==='function'?onFulfilled:(value)=>valueonRejected=typeofonRejected==='function'?onRejected:(reason)=>{throwreason}constp2=newPromise((resolve,reject)=>{if(that.status===FULFILLED){//调用成功回调constx=onFulfilled(that.value)resolvePromise(x,resolve,reject)}elseif(that.status===REJECTED){//调用失败回调constx=onRejected(that.reason)resolvePromise(x,resolve,reject)}else{//挂载状态下缓存所有的成功和失败回调that.onFulfilled.push(onFulfilled)that.onRejected.push(onRejected)}})returnp2}functionresolvePromise(x,resolve,reject){//如果x是promise对象执行其then函数(参数为p2的回调函数,而非返回的x的回调函数)if(xinstanceofPromise){x.then((value)=>resolve(value),(reason)=>reject(reason))}else{//如果x是普通值resolve(x)}}module.exports=Promise

继续执行上述三个例子的代码,均可得出success的结果。

如果是普通值,直接resolve返回值x

如果是Promise,执行x.then。注意!!!then函数传参是p2resolvereject,所以执行const x = onFulfilled(that.value)时,执行的是p2resolve函数,从而将值传递下去。

值穿透在没有resolvereject参数的前提下,判断入参是否是函数,不是的话,赋值默认函数。

处理链式调用及值穿透(异步)

main.js内逻辑更改为异步的情况。

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{setTimeout(()=>{resolve('success')})})...

来分析下原因:首先p.thenp.then.then都是第一轮事件循环,所以会于setTimeout去执行。其次,异步的情况下,我们是缓存成功和回调函数,等待resolvereject的时候再去执行。但我们缓存的仅仅是回调函数,并不是一个promise对象,所以只要再包装一层即可。

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise0

处理返回自身的情况

原生promise会将与返回与自身相等的错误情况抛出Chaining cycle detected for promise #<Promise>

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise1

resolvePromise增加p2传参,并添加判断

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise2

执行报错p1 is not defined,查看堆栈报错由于resolvePromise(p2, x, resolve, reject)中的p2未初始化。

根据promise/A+规范提示,可巧妙利用宏任务/微任务去解决这个问题,这边我选择setTimeout宏任务的形式。

更改代码如下:

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise3

增加错误处理

主要捕获构造函数then函数的错误。用try/catch形式捕获

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise4

拿个?验证

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise5

标准版Promise

丐版Promise已经基本满足所有情况,但是总想能有个证书,那还得符合我们Promise/A+规范。

npm init

npm install promises-aplus-tests --save-dev

添加包的方法

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise6

package.jsonscripts字段更改命令为test: promises-aplus-tests promise,并执行npm run test

执行,不出所料,一堆报错。根据提示需按照规范需更改resolvePromise函数

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise7

至此,标准版Promise更改完毕。

Promise周边

Promise.resolve

Promise.resolve(value)

返回一个给定值解析后的promise对象。

如果当前值是promise对象,返回这个promise

如果是个带有then函数的对象,采用它的最终状态。

返回当前值作为完成状态的promise

手写实现

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise8

Promise.reject

返回一个带有拒绝原因的Promise对象。

基本实现

//promise.jsconstPENGDING='pending'constFULFILLED='fulfilled'constREJECTED='rejected'functionPromise(executor){letthat=this//初始状态为pengdingthis.status=PENGDING//记录成功返回的结果值this.value=null//记录失败返回的结果值this.reason=nullfunctionresolve(value){if(that.status===PENGDING){that.status=FULFILLEDthat.value=value}}functionreject(reason){if(that.status===PENGDING){that.status=REJECTEDthat.reason=reason}}executor(resolve,reject)}Promise.prototype.then=function(onFulfilled,onRejected){if(this.status===FULFILLED){//调用成功回调onFulfilled(this.value)}elseif(this.status===REJECTED){//调用失败回调onRejected(this.reason)}}module.exports=Promise9

Promise.all

Promise.all(iterable)

Promise.all接收一个promise的iterable类型(Array、Map、Set)的输入,并且只返回一个promise实例。该实例的resolve是在所有的promiseresolve回调结束后执行。reject是在任一个promise执行reject后就会立即抛出错误。

基本使用

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})0

手写实现

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})1

Promise.race

返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

基本使用

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})2

手写实现

//main.jsconstPromise=require('./promise')constp=newPromise((resolve,reject)=>{resolve('success')reject('error')})p.then((value)=>{console.log(value)},(reason)=>{console.log(reason)})3

总结

如果觉得有帮助的,毫不吝啬地点个?呗。

作者:瑾行链接:https://juejin.cn/post/6998888561663541255著作权归作者所有。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/691.html