游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

由浅入深,从掌握Promise的基本使用到手写Promise

  前言
  在ES6之前,对于一些异步任务的处理始终没有很好的方案可以解决,处理异步的方案可谓是十分混乱,在业务需求下异步请求的套用,就形成了回调地狱,严重影响代码的阅读性。而Promise的出现,给我们统一了规范,解决了之前处理异步任务的许多痛点,并且它友好的使用方式,使之成为了JavaScript一大重点,同时也是面试的高频问点,下面就一起来全面认识一下Promise吧。1。什么是Promise?
  如果我们想在一个异步请求之后,拿到请求的结果,在ES6之前我们可以怎么做呢?
  比如,给定一个请求地址,希望拿到它请求成功或者失败的结果:可以通过分别设置成功和失败的两个回调;当请求成功后调用成功的回调,将成功的结果传递过去;当请求失败后调用失败的回调,将失败的结果传递过去;functionrequest(url,successCb,failCb){setTimeout(function(){if(urlaaabbb){请求成功letres〔1,2,3〕successCb(res)}else{请求失败leterrerrmessagefailCb(err)}})}调用方式,从回调中拿结果request(aaabbb,function(res){console。log(res)},function(err){console。log(err)})
  将上面的情况使用Promise来实现一下:Promise是一个类,通过new调用,可以给予调用者一个承诺;通过new创建Promise对象时,需要传入一个回调函数,这个回调函数称之为executor,executor接收两个参数resolve和reject;传入的回调会被立即执行,当调用resolve函数时,会去执行Promise对象的then方法中传入的成功回调;当调用reject函数时,会去执行Promise对象的then方法中传入的失败回调函数,并且请求后的结果可以通过参数传递过去;functionrequest(url){returnnewPromise((resolve,reject){setTimeout((){if(urlaaabbb){letres〔1,2,3〕resolve(res)请求成功调用resolve}else{leterrerrmessagereject(err)请求失败调用reject}})})}constprequest(aaabbb)p。then(res{console。log(res)拿到resolve传递过来的值},err{console。log(err)拿到reject传递过来的值})2。Promise的三种状态
  为什么Promise能够将请求的结果准确的传递到then中的回调函数中,因为Promise其核心就用三种状态来进行管控。待定状态(pending):Promise的初始状态;已兑现(resolved、fulfilled):操作成功,如执行resolve时就变为该状态;已拒绝(rejected):操作失败,如执行reject时就变为该状态;
  通过上面的案例,可以在浏览器中查看Promise分别在执行resolve和reject后的打印结果和Promise当时处于的状态:resolve和reject都没执行:
  执行resolve,请求成功:
  执行reject,请求失败:
  注意:在后续的对Promise的讲述过程中,都需要带着Promise的状态去理解。3。executor
  executor是在创建Promise是需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数,分别就是resolve和reject。newPromise((resolve,reject){console。log(我是executor中的代码,我会被立即执行)})
  通常我们会在executor中确定Promise的状态,而且状态一旦被确定下来,Promise的状态就会被锁死,即Promise的状态一旦修改,就不能再次更改了。当调用resolve,如果resolve传入的值不是一个Promise(即传入的值为一个普通值),Promise的状态就会立即变成fulfilled;但是,如果在resolve后接着调用reject,是不会有任何的效果的,因为reject已经无法改变Promise的结果了;4。resolve的参数
  上面聊到了resolve需要传入一个普通值,Promise的状态才会被立即锁定为fulfilled,那么如果传递的不是普通值呢?一般resolve传递以下三类值,会有不同的表现效果。传值一:resolve传入一个普通值或普通对象,那么这个值会作为then中第一个回调的参数;constpnewPromise((resolve,reject){resolve(123)})p。then(res{console。log(res)123})传值二:resolve传入一个Promise,那么这个传入的Promise会决定原来Promise的状态;传入的Promise调用的是resolve;constnewPnewPromise((resolve,reject){resolve(123)})constpnewPromise((resolve,reject){resolve(newP)})p。then(res{console。log(res)123},err{console。log(err)})传入的Promise调用的是reject;constnewPnewPromise((resolve,reject){reject(errmessage)})constpnewPromise((resolve,reject){resolve(newP)})p。then(res{console。log(res)},err{console。log(err)errmessage})传值三:resolve传入一个特殊对象,该对象中实现了then方法,那么Promise的状态就是对象中then方法执行后的结果来决定的;then中执行了resolve;constobj{then:function(resolve,reject){resolve(123)}}constpnewPromise((resolve,reject){resolve(obj)})p。then(res{console。log(res)123},err{console。log(err)})then中执行了reject;constobj{then:function(resolve,reject){reject(errmessage)}}constpnewPromise((resolve,reject){resolve(obj)})p。then(res{console。log(res)},err{console。log(err)errmessage})5。Promise相关实例方法
  Promise的实例方法,就是可以通过其实例对象进行调用的方法。5。1。then方法
  then方法是Promise实例对象上的一个方法:Promise。prototype。then
  (1)then方法接收两个参数状态变成fulfilled的回调函数;状态变成rejected的回调函数;promise。then(res{console。log(状态变成fulfilled回调)},err{console。log(状态变成rejected回调)})
  (2)then方法多次调用一个Promise的then方法是可以被多次调用的,每次调用都可以传入对应的fulfilled回调;当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;反之,当Promise的状态变成rejected,所有then中传入的rejected回调都会被执行;constpnewPromise((resolve,reject){resolve(aaa)})p。then(res{console。log(res)aaa})p。then(res{console。log(res)aaa})p。then(res{console。log(res)aaa})
  (3)then方法中的返回值
  then调用本身是有返回值的,并且它的返回值是一个Promise,所以then可以进行链式调用,但是then方法调用的返回值的状态是什么呢?主要是由其返回值决定的。当then方法中的回调在执行时处于pending状态;当then方法中的回调返回一个结果时处于fulfilled状态,并且会将结果作为resolve的参数;返回一个普通的值:这个普通的值会被作为一个新Promise的resolve中的值p。then(res{return123相当于:returnnewPromise((resolve,reject){resolve(123)})})。then(res{console。log(res)123})返回一个实现了then方法的对象:p。then(res{constobj{then:function(resolve,reject){resolve(abc)}}returnobj相当于:returnnewPromise((resolve,reject){resolve(obj。then)})})。then(res{console。log(res)abc})返回一个Promise:p。then(res{constnewPnewPromise((resolve,reject){resolve(123)})returnnewP相当于:constnewPnewPromise((resolve,reject){resolve(123)})returnnewPromise((resolve,reject){resolve(newP)})})。then(res{console。log(res)123})当then方法执行时抛出一个异常,就处于rejected状态,同样,Promise的executor在执行的时候抛出异常,Promise对应的状态也会变成rejected;constpnewPromise((resolve,reject){thrownewError(errmessage)})p。then(res{console。log(res)},err{console。log(err)Error:errmessagereturnnewError(thenerrmessage)})。then(res{console。log(res)},err{console。log(err)Error:thenerrmessage})5。2。catch方法
  catch方法是Promise实例对象上的一个方法:Promise。prototype。catch
  (1)catch方法可多次调用一个Promise的catch方法也是可以被多次调用的,每次调用都可以传入对应的reject回调;当Promise的状态变成rejected的时候,这些回调就都会执行;catch方法的效果和then方法的第二个回调函数差不多,可用于替代then方法的第二个回调;constpnewPromise((resolve,reject){reject(errmessage)})p。catch(err{console。log(err)errmessage})p。catch(err{console。log(err)errmessage})p。catch(err{console。log(err)errmessage})
  (2)catch方法的返回值catch方法的执行返回的也是一个Promise对象,使用catch后面可以继续调用then方法或者catch方法;如果在catch后面调用then方法,会进入到then方法的fulfilled回调函数中,因为catch返回的Promise默认是fulfilled;p。catch(err{returncatchreturnvalue})。then(res{console。log(res)catchreturnvalue})如果catch后续又调用了catch,那么可以抛出一个异常,就会进入后面的catch回调中;p。catch(err{thrownewError(catcherrmessage)})。catch(err{console。log(err)Error:catcherrmessage})
  (3)catch的作用catch主要是用于捕获异常的,当executor抛出异常时,可以通过catch进行处理;注意:当Promise的executor执行reject或者抛出异常,后续必须要有捕获异常的处理,如下代码,虽然都调用了then方法,接着后续又调用了catch方法,但是then和catch是两次独立的调用,两次调用并没有联系,所以就被认定为没有处理异常。constpnewPromise((resolve,reject){reject(errmessage)})p。then(res{console。log(res)})p。catch(err{console。log(err)})
  正确处理的方法为:方法一:p。then(res{console。log(res)})。catch(err{console。log(err)})方法二:p。then(res{console。log(res)},err{console。log(err)})5。3。finally方法
  finally方法是Promise实例对象上的一个方法:Promise。prototype。finallyfinally是在ES9中新增的,无论Promise的状态变成fulfilled还是rejected,最终都会执行finally中的回调;注意finally是不接收参数的,因为它必定执行;constpnewPromise((resolve,reject){resolve(123)})p。then(res{console。log(res)123})。catch(err{console。log(err)})。finally((){console。log(finallycode)finallycode})6。Promise相关类方法
  Promise的类方法,就是直接通过Promise进行调用。6。1。resolve方法
  resolve方法具体有什么用呢?当我们希望将一个值转成Promise来使用,就可以通过直接调用resolve方法来实现,其效果就相当于在new一个Promise时在executor中执行了resolve方法。
  resolve传入的参数类型:参数为一个普通的值;constpPromise。resolve(aaaa)相当于:constpnewPromise((resolve,reject){resolve(aaaa)})console。log(p)
  参数为一个实现了then方法的对象;constpPromise。resolve({then:function(resolve,reject){resolve(aaaa)}})相当于:constpnewPromise((resolve,reject){resolve({then:function(resolve,reject){resolve(aaaa)}})})console。log(p)
  参数为一个Promise;constpPromise。resolve(newPromise((resolve,reject){resolve(abc)}))相当于:constpnewPromise((resolve,reject){resolve(newPromise((resolve,reject){resolve(abc)}))})console。log(p)
  6。2。reject方法
  reject方法和resolve的用法一致,只不过是将可以得到一个状态为rejected的Promise对象,并且reject不过传入的是什么参数,都会原封不动作为rejected状态传递到catch中。1。传入普通值constp1Promise。reject(123)p1。then(res{console。log(res)})。catch(err{console。log(err:,err)})2。传入实现then方法对象constp2Promise。reject({then:function(resolve,reject){resolve(aaaa)}})p2。then(res{console。log(res)})。catch(err{console。log(err:,err)})3。传入Promiseconstp3Promise。reject(newPromise((resolve,reject){resolve(aaaa)}))p3。then(res{console。log(res)})。catch(err{console。log(err:,err)})
  6。3。all方法
  all方法可以接收由多个Promise对象组成的数组(准确来说是可接收一个可迭代对象),all方法调用返回的Promise状态,由所有Promise对象共同决定。当传入的所有Promise对象的状态都为fulfilled是,all方法返回的Promise状态就为fulfilled,并且会将所有Promise对象的返回值组成一个数组;constp1newPromise((resolve,reject){setTimeout((){resolve(111)},1000)})constp2newPromise((resolve,reject){setTimeout((){resolve(222)},2000)})constp3newPromise((resolve,reject){setTimeout((){resolve(333)},3000)})Promise。all(〔p1,p2,p3〕)。then(res{console。log(res:,res)res:〔111,222,333〕})。catch(err{console。log(err:,err)})当传入的Promise有一个变成了rejected状态,那么就会获取第一个变成rejected状态的返回值作为all方法返回的Promise状态;constp1newPromise((resolve,reject){setTimeout((){resolve(111)},1000)})constp2newPromise((resolve,reject){setTimeout((){reject(errmessage)},2000)})constp3newPromise((resolve,reject){setTimeout((){resolve(333)},3000)})Promise。all(〔p1,p2,p3〕)。then(res{console。log(res:,res)})。catch(err{console。log(err:,err)err:errmessage})6。4。allSettled方法
  相比于all方法,allSettled方法不管传入的Promise对象的状态是fulfilled还是rejected,最终都会讲结果返回,并且返回的结果是一个数组,数组中存放着每一个Promise对应的状态status和对应的值value。constp1newPromise((resolve,reject){setTimeout((){resolve(111)},1000)})constp2newPromise((resolve,reject){setTimeout((){reject(errmessage)},2000)})constp3newPromise((resolve,reject){setTimeout((){resolve(333)},3000)})Promise。allSettled(〔p1,p2,p3〕)。then(res{console。log(res:,res)})。catch(err{console。log(err:,err)})
  6。5。race方法
  race翻译为竞争,顾名思义哪一个Promise对象最先返回结果,就使用最先返回结果的Promise状态。
  一下代码是p1最先有结果的,p1中执行的是resolve,所以返回的状态为fulfilled:constp1newPromise((resolve,reject){setTimeout((){resolve(111)},1000)})constp2newPromise((resolve,reject){setTimeout((){reject(errmessage)},2000)})constp3newPromise((resolve,reject){setTimeout((){resolve(333)},3000)})Promise。race(〔p1,p2,p3〕)。then(res{console。log(res:,res)res:111})。catch(err{console。log(err:,err)})6。6。any方法
  any方法是ES12中新增的方法,与race是类似的,any方法会等到有一个fulfilled状态的Promise,才会决定any调用返回新Promise的状态(也就是说any一定会等到有一个Promise状态为fullfilled)。
  那么,如果所有的Promise对象的状态都变为了rejected呢?最终就会报一个AggregateError错误,如果想拿到所有的rejected状态的返回值,可以通过在捕获异常回调参数中的errors获取:constp1newPromise((resolve,reject){setTimeout((){reject(errmessage1)},1000)})constp2newPromise((resolve,reject){setTimeout((){reject(errmessage2)},2000)})constp3newPromise((resolve,reject){setTimeout((){reject(errmessage3)},3000)})Promise。any(〔p1,p2,p3〕)。then(res{console。log(res:,res)})。catch(err{console。log(err)console。log(err。errors)})
  注意:any方法是ES12新增的,node版本过低的话是会报错找不到any方法的,可以在浏览器中测试。7。手写Promise
  掌握了以上Promise的用法,那么就一步步来实现一下Promise吧。7。1。executor的实现创建一个类,这个类可接收一个executor函数;executor函数需传入两个函数resolve和reject,并且executor是需要立即执行的;创建三个常量用于管理Promise的三种状态;一旦Promise的状态改变就不能再次被修改;还需将传入resolve和reject的参数值进行保存,便于后续then的使用;定义Promise的三种状态常量constPENDINGSTATUSpendingconstFULFILLEDSTATUSfulfilledconstREJECTEDSTATUSrejectedclassMyPromise{constructor(executor){初始化Promise的状态为pendingthis。promiseStatusPENDINGSTATUS初始化变量,用于保存resolve和reject传入的参数值this。valueundefinedthis。reasonundefined1。定义executor需要传入的resolve函数constresolve(value){只有当Promise的状态为pending,才能将状态改变fulfilledif(this。promiseStatusPENDINGSTATUS){this。promiseStatusFULFILLEDSTATUSthis。valuevalueconsole。log(调用了resolve,状态变成fulfilled啦)}}2。定义executor需要传入的reject函数constreject(reason){只有当Promise的状态为pending,才能将状态改变为rejectedif(this。promiseStatusPENDINGSTATUS){this。promiseStatusREJECTEDSTATUSthis。reasonreasonconsole。log(调用了reject,状态变成rejected啦)}}3。将定义的两个函数传入executor并调用executor(resolve,reject)}}
  简单测试一下:先调用resolvenewMyPromise((resolve,reject){resolve()reject()})先调用rejectnewMyPromise((resolve,reject){reject()resolve()})
  7。2。then方法的实现
  (1)then基本实现then方法接收两个参数:onFulfilled回调:当Promise状态变为fulfilled需要执行的回调;onRejected回调:当Promise状态变为rejected需要执行的回调classMyPromise{constructor(executor){初始化Promise的状态为pendingthis。promiseStatusPENDINGSTATUS初始化变量,用于保存resolve和reject传入的参数值this。valueundefinedthis。reasonundefined1。定义executor需要传入的resolve函数constresolve(value){只有当Promise的状态为pending,才能将状态改变fulfilledif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusFULFILLEDSTATUSthis。valuevalue状态变成fulfilled就去调用onFulfilledthis。onFulfilled(this。value)})}}2。定义executor需要传入的reject函数constreject(reason){只有当Promise的状态为pending,才能将状态改变为rejectedif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusREJECTEDSTATUSthis。reasonreason状态变成rejected就去调用onRejectedthis。onRejected(this。reason)})}}3。将定义的两个函数传入executor并调用executor(resolve,reject)}then(onFulfilled,onRejected){保存fulfilled和rejected状态的回调this。onFulfilledonFulfilledthis。onRejectedonRejected}}
  注意:这里将onFulfilled和onRejected的调动放在了queueMicrotask,在JavaScript中可以通过queueMicrotask使用微任务,而原Promise的then中回调的执行,也是会被放在微任务中的,为什么要放在微任务中呢?
  原因:如果不使用微任务,那么在executor中执行resolve或者reject时,then方法还没被调用,onFulfilled和onRejected就都还没被赋值,所以调用时会报错,加入微任务就可以实现将onFulfilled和onRejected的调用推迟到下一次事件循环,也就是等then调用后赋值了才会执行。
  简单测试一下:constp1newMyPromise((resolve,reject){resolve(aaaa)})constp2newMyPromise((resolve,reject){reject(errmessage)})p1。then(res{console。log(res)aaaa},err{console。log(err)})p2。then(res{console。log(res)},err{console。log(err)errmessage})
  (2)then优化一对于以上then的基本实现,还存在一些不足之处,比如:then方法是可以进行多次调用的,并且每一次调用都是独立调用,互不影响,所以需要收集当Promise状态改变时,对应需要执行哪些回调,需用数组进行收集;如果then是放到定时器中调用的,那么改then的回调是不会被调用的,因为在前面我们是通过将回调添加到微任务中执行的,而定时器是宏任务,会在微任务执行完成后执行,所以定时器中then的回调就没有被调用;当then是放到定时器中执行的,那么执行的时候,微任务已经执行完成了,Promise状态肯定也确定下来了,那么只需要直接调用then中的回调即可;classMyPromise{constructor(executor){初始化Promise的状态为pendingthis。promiseStatusPENDINGSTATUS初始化变量,用于保存resolve和reject传入的参数值this。valueundefinedthis。reasonundefined初始化两个数组,分别用于保存then中对应需要执行的回调this。onFulfilledFns〔〕this。onRejectedFns〔〕1。定义executor需要传入的resolve函数constresolve(value){只有当Promise的状态为pending,才能将状态改变fulfilledif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusFULFILLEDSTATUSthis。valuevalue状态变成fulfilled就去遍历调用onFulfilledthis。onFulfilledFns。forEach(fn{fn(this。value)})})}}2。定义executor需要传入的reject函数constreject(reason){只有当Promise的状态为pending,才能将状态改变为rejectedif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusREJECTEDSTATUSthis。reasonreason状态变成rejected就去遍历调用onRejectedthis。onRejectedFns。forEach(fn{fn(this。reason)})})}}3。将定义的两个函数传入executor并调用executor(resolve,reject)}then(onFulfilled,onRejected){1。如果在调用then时,Promise的状态已经确定了,就直接执行回调if(this。promiseStatusFULFILLEDSTATUSonFulfilled){onFulfilled(this。value)}if(this。promiseStatusREJECTEDSTATUSonRejected){onRejected(this。reason)}2。如果调用then时,Promise的状态还没确定下来,就放入微任务中执行if(this。promiseStatusPENDINGSTATUS){将then中成功的回调和失败的回调分别存入数组中this。onFulfilledFns。push(onFulfilled)this。onRejectedFns。push(onRejected)}}}
  简单测试一下:constpnewMyPromise((resolve,reject){resolve(aaaa)reject(errmessage)})p。then(res{console。log(res1:,res)},err{console。log(err1:,err)})p。then(res{console。log(res2:,res)},err{console。log(err1:,err)})setTimeout((){p。then(res{console。log(res3:,res)},err{console。log(err1:,err)})})
  (3)then优化二通过上一步优化,then方法还存在一个缺陷,就是不能进行链式调用,在前面讲then方法时,then方法执行的返回值是一个promise对象,并且返回的promise状态是由then方法中回调函数的返回值决定的,then中必定需要返回一个新的Promise;上一个then中回调的返回值可以传递到下一个then中成功的回调中,也就是返回的promise执行了resolve方法,那么什么时候可以传递到下一个then中失败的回调中呢?只需要上一个then中抛出异常即可,相当于返回的promise执行了reject方法;所以,在这里需要拿到then中回调函数返回的结果,并且需要通过trycatch判断是调用resolve还是reject;注意:如果是在executor中就抛出了异常,也需要通过trycatch去执行executor;classMyPromise{constructor(executor){初始化Promise的状态为pendingthis。promiseStatusPENDINGSTATUS初始化变量,用于保存resolve和reject传入的参数值this。valueundefinedthis。reasonundefined初始化两个数组,分别用于保存then中对应需要执行的回调this。onFulfilledFns〔〕this。onRejectedFns〔〕1。定义executor需要传入的resolve函数constresolve(value){只有当Promise的状态为pending,才能将状态改变fulfilledif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusFULFILLEDSTATUSthis。valuevalue状态变成fulfilled就去遍历调用onFulfilledthis。onFulfilledFns。forEach(fn{fn(this。value)})})}}2。定义executor需要传入的reject函数constreject(reason){只有当Promise的状态为pending,才能将状态改变为rejectedif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusREJECTEDSTATUSthis。reasonreason状态变成rejected就去遍历调用onRejectedthis。onRejectedFns。forEach(fn{fn(this。reason)})})}}3。将定义的两个函数传入executor并调用如果executor中就抛出了异常,那么直接执行reject即可try{executor(resolve,reject)}catch(err){reject(err)}}then(onFulfilled,onRejected){returnnewMyPromise((resolve,reject){1。如果在调用then时,Promise的状态已经确定了,就直接执行回调if(this。promiseStatusFULFILLEDSTATUSonFulfilled){通过trycatch捕获异常,没有捕获到执行resolve,捕获到执行rejecttry{constvalueonFulfilled(this。value)resolve(value)}catch(err){reject(err)}}if(this。promiseStatusREJECTEDSTATUSonRejected){try{constreasononRejected(this。reason)resolve(reason)}catch(err){reject(err)}}2。如果调用then时,Promise的状态还没确定下来,就放入微任务中执行if(this。promiseStatusPENDINGSTATUS){将then中成功的回调和失败的回调分别存入数组中将传入的回调外包裹一层函数,目的是为了这里能够拿到then中回调执行的结果this。onFulfilledFns。push((){try{constvalueonFulfilled(this。value)resolve(value)}catch(err){reject(err)}})this。onRejectedFns。push((){try{constreasononRejected(this。reason)resolve(reason)}catch(err){reject(err)}})}})}}
  简单测试一下:constpnewMyPromise((resolve,reject){resolve(aaaa)})p。then(res{console。log(res1:,res)returnbbbb},err{console。log(err1:,err)})。then(res{console。log(res2:,res)thrownewError(errmessage)},err{console。log(err2:,err)})。then(res{console。log(res3:,res)},err{console。log(err3:,err)})
  7。3。catch方法的实现catch方法的功能类似于then方法中的失败回调,所以,实现catch方法只需要调用then,给then传入失败的回调即可;只给then传入一个回调,意味着根据上面的代码,我们还需要对then的回调进行条件判断,有值才添加到对应数组中;注意:在then后链式调用catch会有一个问题,调用catch方法的promise是then执行之后返回的新promise,而catch真正需要去调用的是当前then的失败回调,而不是当前then执行后结果promise的失败回调,所以,可以将当前then的失败回调推到下一次的promise中,而抛出异常就可以实现(因为上一个then抛出异常,可以传递到下一个then的失败回调中)catch方法实现catch(onRejected){returnthis。then(undefined,onRejected)}
  then方法改进:
  简单测试一下:constpnewMyPromise((resolve,reject){reject(errmessage)})p。then(res{console。log(res)})。catch(err{console。log(err)errmessage})7。4。finally方法的实现finally方法不管是Promise状态变成fulfilled还是rejected都会被执行;这里可以巧妙的借助then方法,不管then是执行成功的回调还是失败的回调,都去执行finally中的回调即可;注意:如果在finally之前使用了catch,因为catch的实现也是去调用then,并且给then的成功回调传递的是undefined,那么执行到catch可能出现断层的现象,导致不会执行到finally,也可以通过在then中添加判断解决;finally(onFinally){this。then((){onFinally()},(){onFinally()})也可直接简写成:this。then(onFinally,onFinally)}
  then方法改进:
  简单测试一下:constpnewMyPromise((resolve,reject){resolve(aaaa)})p。then(res{console。log(res:,res)res:aaaa})。catch(err{console。log(err:,err)})。finally((){console。log(我是一定会执行的!)我是一定会执行的!})7。5。resolve和reject方法的实现resolve和reject类方法的实现就是去调用Promise中executor中的resolve和reject;注意:类方法需要加上static关键字;staticresolve(value){returnnewMyPromise((resolve,reject)resolve(value))}staticreject(reasion){returnnewMyPromise((resolve,reject)reject(reasion))}
  简单测试一下:MyPromise。resolve(aaaa)。then(res{console。log(res)aaaa})MyPromise。reject(bbbb)。then(res{console。log(res)},err{console。log(err)bbbb})7。6。all方法的实现all方法可接收一个promise数组,当所有promise状态都变为fulfilled,就返回所有promise成功的回调值(一个数组),当其中有一个promise状态变为了rejected,就返回该promise的状态;all实现的关键:当所有promise状态变为fulfilled就去调用resolve,当有一个promise状态变为rejected就去调用reject;staticall(promises){returnnewMyPromise((resolve,reject){用于存放所有成功的返回值constresults〔〕promises。forEach(promise{promise。then(res{results。push(res)当成功返回值的长度与传入promises的长度相等,就调用resolveif(results。lengthpromises。length){resolve(results)}},err{一旦有一个promise变成了rejected状态,就调用rejectreject(err)})})})}
  简单测试一下:constp1newMyPromise(resolve{setTimeout((){resolve(aaaa)},1000)})constp2newMyPromise(resolve{setTimeout((){resolve(bbbb)},2000)})constp3newMyPromise(resolve{setTimeout((){resolve(cccc)},3000)})MyPromise。all(〔p1,p2,p3〕)。then(res{console。log(res)〔aaaa,bbbb,cccc〕})。catch(err{console。log(err)})7。7。allSettled方法的实现allSettled方法会返回所有promise的结果数组,数组中包含每一个promise的状态和值;不管promise的状态为什么,最终都会调用resolve;staticallSettled(promises){returnnewMyPromise((resolve,reject){用于存放所有promise的状态和返回值constresults〔〕promises。forEach(promise{promise。then(res{results。push({status:FULFILLEDSTATUS,value:res})当长度相等,调用resolveif(results。lengthpromises。length){resolve(results)}},err{results。push({status:REJECTEDSTATUS,value:err})当长度相等,调用resolveif(results。lengthpromises。length){resolve(results)}})})})}
  简单测试一下:constp1newMyPromise(resolve{setTimeout((){resolve(aaaa)},1000)})constp2newMyPromise((resolve,reject){setTimeout((){reject(errmessage)},2000)})constp3newMyPromise(resolve{setTimeout((){resolve(bbbb)},3000)})MyPromise。allSettled(〔p1,p2,p3〕)。then(res{console。log(res)})。catch(err{console。log(err)})
  7。8。race方法的实现race方法是获取最先改变状态的Promise,并以该Promise的状态作为自己的状态;staticrace(promises){returnnewMyPromise((resolve,reject){promises。forEach(promise{得到状态最先改变的promise,调用对应的resolve和rejectpromise。then(res{resolve(resolve)},err{reject(err)})})})}7。9。any方法的实现any方法会等到有一个Promise的状态变成fulfilled,最终就是fulfilled状态;如果传入的所有Promise都为rejected状态,会返回一个AggregateError,并且可以在AggregateError中的errors属性中获取所有错误信息;staticany(promises){returnnewMyPromise((resolve,reject){用于记录状态为rejected的值constreasons〔〕promises。forEach(promise{promise。then(res{当有一个promise变成fulfilled状态就调用resolveresolve(res)},err{reasons。push(err)当所有promise都是rejected就调用reject,并且传入AggregateErrorif(reasons。lengthpromises。length){reject(newAggregateError(reasons))}})})})}
  简单测试一下:constp1newMyPromise((resolve,reject){setTimeout((){reject(errmessage1)},1000)})constp2newMyPromise((resolve,reject){setTimeout((){reject(errmessage2)},2000)})constp3newMyPromise((resolve,reject){setTimeout((){reject(errmessage3)},3000)})MyPromise。any(〔p1,p2,p3〕)。then(res{console。log(res)})。catch(err{console。log(err:,err)console。log(err。errors)})
  7。10。Promise手写完整版整理
  上面已经对Promise的各个功能进行了实现,下面就来整理一下最终的完整版,可以将一些重复的逻辑抽取出去,比如trycatch。定义Promise的三种状态常量constPENDINGSTATUSpendingconstFULFILLEDSTATUSfulfilledconstREJECTEDSTATUSrejectedtrycatch逻辑抽取functiontryCatchFn(execFn,value,resolve,reject){try{constresultexecFn(value)resolve(result)}catch(err){reject(err)}}classMyPromise{constructor(executor){初始化Promise的状态为pendingthis。promiseStatusPENDINGSTATUS初始化变量,用于保存resolve和reject传入的参数值this。valueundefinedthis。reasonundefined初始化两个数组,分别用于保存then中对应需要执行的回调this。onFulfilledFns〔〕this。onRejectedFns〔〕1。定义executor需要传入的resolve函数constresolve(value){只有当Promise的状态为pending,才能将状态改变fulfilledif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusFULFILLEDSTATUSthis。valuevalue状态变成fulfilled就去遍历调用onFulfilledthis。onFulfilledFns。forEach(fn{fn(this。value)})})}}2。定义executor需要传入的reject函数constreject(reason){只有当Promise的状态为pending,才能将状态改变为rejectedif(this。promiseStatusPENDINGSTATUS){添加微任务queueMicrotask((){如果Promise状态不为pending,后面的代码就不再执行了if(this。promiseStatus!PENDINGSTATUS)returnthis。promiseStatusREJECTEDSTATUSthis。reasonreason状态变成rejected就去遍历调用onRejectedthis。onRejectedFns。forEach(fn{fn(this。reason)})})}}3。将定义的两个函数传入executor并调用如果executor中就抛出了异常,那么直接执行reject即可try{executor(resolve,reject)}catch(err){reject(err)}}then(onFulfilled,onRejected){判断onRejected是否有值,没有值的话直接赋值一个抛出异常的方法,用于传递到下一次then中的失败回调,供catch调用onRejectedonRejected(err{throwerr})判断onFulfilled是否有值,避免在使用catch时传入的undefined不会执行,出现断层现象onFulfilledonFulfilled(valuevalue)returnnewMyPromise((resolve,reject){1。如果在调用then时,Promise的状态已经确定了,就直接执行回调if(this。promiseStatusFULFILLEDSTATUS){通过trycatch捕获异常,没有捕获到执行resolve,捕获到执行rejecttryCatchFn(onFulfilled,this。value,resolve,reject)}if(this。promiseStatusREJECTEDSTATUS){tryCatchFn(onRejected,this。reason,resolve,reject)}2。如果调用then时,Promise的状态还没确定下来,就放入微任务中执行if(this。promiseStatusPENDINGSTATUS){将then中成功的回调和失败的回调分别存入数组中将传入的回调外包裹一层函数,目的是为了这里能够拿到then中回调执行的结果this。onFulfilledFns。push((){tryCatchFn(onFulfilled,this。value,resolve,reject)})this。onRejectedFns。push((){tryCatchFn(onRejected,this。reason,resolve,reject)})}})}catch(onRejected){returnthis。then(undefined,onRejected)}finally(onFinally){this。then(onFinally,onFinally)}staticresolve(value){returnnewMyPromise((resolve,reject)resolve(value))}staticreject(reasion){returnnewMyPromise((resolve,reject)reject(reasion))}staticall(promises){returnnewMyPromise((resolve,reject){用于存放所有成功的返回值constresults〔〕promises。forEach(promise{promise。then(res{results。push(res)当成功返回值的长度与传入promises的长度相等,就调用resolveif(results。lengthpromises。length){resolve(results)}},err{一旦有一个promise变成了rejected状态,就调用rejectreject(err)})})})}staticallSettled(promises){returnnewMyPromise((resolve,reject){用于存放所有promise的状态和返回值constresults〔〕promises。forEach(promise{promise。then(res{results。push({status:FULFILLEDSTATUS,value:res})当长度相等,调用resolveif(results。lengthpromises。length){resolve(results)}},err{results。push({status:REJECTEDSTATUS,value:err})当长度相等,调用resolveif(results。lengthpromises。length){resolve(results)}})})})}staticrace(promises){returnnewMyPromise((resolve,reject){promises。forEach(promise{得到状态最先改变的promise,调用对应的resolve和rejectpromise。then(resolve,reject)})})}staticany(promises){returnnewMyPromise((resolve,reject){用于记录状态为rejected的值constreasons〔〕promises。forEach(promise{promise。then(res{当有一个promise变成fulfilled状态就调用resolveresolve(res)},err{reasons。push(err)当所有promise都是rejected就调用reject,并且传入AggregateErrorif(reasons。lengthpromises。length){reject(newAggregateError(reasons))}})})})}}
  本文作者:MomentYY
  本文链接:https:www。cnblogs。comMomentYYp16096666。html

健康养生做做防感冒操感冒绕道走春季冷热无常,人们很容易感冒。预防感冒,不妨平时经常做做防感冒操。第一步:擦脸预备姿势:身体直立,两脚分开与肩等宽。擦脸动作:曲肘,五指并拢,中指贴鼻翼两侧,……青春痘需要用药吗?青春痘需要治疗吗?青春痘可以说是让人非常烦恼的皮肤疾病了,很多人不知道青春痘需不需要去治疗,下面5号网的小编为你们介绍青春痘需要用药吗?青春痘需要治疗吗?青春痘需要用药吗看青春痘的严重程度……28岁字节员工确认离世,给互联网打工人的几点警示想必大家都已经知道最近字节发生的员工猝死事件,如果还不知道,先简单回顾一下3月23日,就在上周,网上流传一份微信聊天记录,这份聊天记录,是字条跳动猝死员工的妻子找群友求助……受关注!2022超级碗美国本土收视观众破1亿甚至带动当天冬奥2月15日消息,NBCUniversal超级碗LVI比赛直播平均有1。123亿观众收看。超级碗LVI在NBC吸引了9920万观众,在Telemundo上还有190万人,这……壹号本发布锐龙版Onexlayermini掌机中关村在线消息:壹号本近日正式发布了基于AMDZen3锐龙R75800U处理器的OnexPlayermini掌机,该机型将于近期正式开售。据了解,锐龙版OnexPlaye……鹿胎膏可以促进排卵吗?关于排卵的那些事现在很多女性的排卵期都不规律,更有甚者不排卵了,下面5号网的小编为你们介绍鹿胎膏可以促进排卵吗?关于排卵的那些事。鹿胎膏可以促进排卵吗鹿胎膏没有促排卵的作用。鹿胎膏会有怎……rohtoz多效高浓度眼药水效果好吗?眼药水是日常生活中常见的东西,市面上产品也挺多的,下面5号网的小编为你们介绍rohtoz多效高浓度眼药水效果好吗?rohtoz多效高浓度眼药水效果好吗加入日本标准所允许的……漂流可以带小孩吗?漂流带小孩注意事项漂流算得上夏季最刺激爽快的运动了吧,相信很多人都喜欢玩,不过这项运动还是存在着危险的,所以问题来了,漂流是否可以带小孩呢?漂流带小孩注意事项!漂流可以带小孩吗夏天放……组图丨广西这个地方油菜花迎春盛开,太美了图为航拍油菜花地。广西新闻网通讯员滚亿忠摄图为油菜花开。广西新闻网通讯员滚亿忠摄图为油菜花盛开的田间。广西新闻网通讯员滚亿忠摄图为游玩的村民。广西新闻网通讯员……陈式太极拳实用拳法第79式转身摆莲脚的注意事项大家好。接下来我们继续讲解陈氏太极拳实用拳法的第79式转身双摆莲脚这一式子的注意事项。此式总共有4个动作。(一)我们尤其注意第1个动作眼法。虽然眼法向右看,由……一起慢慢变老2014年10月,厦门鼓浪屿轮渡涨价前夕,我和老公终于踏上了这片浪漫而迷人的岛屿。映入眼帘的,是遍地盛开的茶花与玫瑰,袅娜,芳香,仪态万千。岛上游人如织,或欣喜,或疲惫,……中秋可以回娘家过吗?中秋节可以在娘家过吗?中秋佳节本来是团圆的时候,很多人也是十分想念父母,想要回娘家和父母团圆,但是在我国传统习俗中有一些情况是不可以回娘家的。那么,中秋可以回娘家过吗?中秋可以回娘家过吗……
为什么你的孩子玩得开心却玩不下去?冬奥会名场面揭秘兴趣是分级上周末,2022年北京冬奥会圆满落幕。倏忽而过的十六天里,一帧帧满载激情与温度的画面让人念念不忘。无论是最终站上领奖台的冠亚季军,还是咬着牙坚持完赛的默默无闻者,无论是冲线,还……有种不服老叫惠英红穿西装,搭配阔腿牛仔裤,62岁还这么潮惠英红可以说是娱乐圈的老戏骨了吧,她的演技实力一定是得到认可的,而且气质也很突出。虽然如今62岁了,可是她的魅力依然出众,这一次就给自己搭配了一身黑色的西装,成熟稳重,黑色的西……婺源油菜花,故宫杏花,林芝桃花,春天该有的模样你喜欢春天吗?春夏秋冬,每个季节都有自己的特色,唯独春天是我最喜欢的季节,因为在春天很多植物开始萌芽,也是繁花盛开的时候,大地一派生机勃勃,充满了朝气和希望。不知细……叩齿真的有益牙齿保健吗?做好这些让你拥有健康牙齿牙齿的重要性不言而喻,不仅能咀嚼食物、帮助发音,而且对面容美观也有很大影响,甚至还关系到社交活动的进行。因此自古以来人们就非常重视牙齿的保健,其中叩齿就是备受推崇的一种养生方法……感觉36岁的张俪还跟小孩子似的,穿绿色吊带裙清爽高级,短发更春日生活打卡季今天穿什么在很多人眼中,夏季都不适合穿一些格纹服装,多多少少会显得有些厚重,仿佛这个元素就是为了秋冬而生,但是在一些时髦人士的穿搭中,格子服装总是不能缺少。……冬末,周游北京门头沟几条著名山路,网红景点白瀑寺未开放2022年2月22日,星期2,哈哈!北京4,大风,自然就万里无云。错过早高峰,背上行囊,一路西行而去。踏上109国道,高兴,有山有水的地方就是让你心情大悦,头脑放空,眼前……孩子头上的旋越多越聪明吗?别瞎猜,真相在这里闺蜜的宝宝上个月出生了,闺蜜的婆婆高兴得不得了:我们家的小宝贝以后不得了,是个人精。我不明白,这孩子这么小,都还没满月呢,从哪儿能看出来孩子是否聪明呢?回去问了老妈,老妈……明明大屏更受欢迎,为什么苹果不推出5。5英寸的iPhoneS对苹果来说,iPhoneSE系列的存在是必然的。首先,和安卓手机厂商不同的是,苹果拥有研发芯片的实力,而且芯片随着产量的增加可以进一步摊薄成本。因此,苹果可以允许iPhoneS……价值之变取代厂房设备数据成企业最核心资产丨工业互联网3。0深在珠海伊斯佳无人车间的生产线上,一瓶瓶专属定制的护肤品鱼贯而出,值得一提的是,这些产品背面都贴了一个二维码,它就像出生证明,每到一个环节,机器扫描二维码,就立刻明白,需要用到哪……一个人开始走大运的3个征兆生活,总会猝不及防地给我们出些难题,让我们陷入各种僵局与困境。怨天尤人的人,走不出黑暗;沉溺悲伤的人,搏不出精彩;唯有勇敢而无畏的人,才能跨越万难。当绝望的乌云笼罩……全国工程设计大师申作伟当选俄罗斯自然科学院外籍院士大众报业大众日报客户端2022061822:53:58近日,俄罗斯自然科学院(PAEHRANS:RussianAcademyofNaturalSciences)院长O。L……冬季撞色总是穿不好?可以试试黑白配,完美拯救穿搭平凡的你黑白配无论是春夏秋冬,我们的衣橱里都离不开黑白单品。而我最喜欢的搭配组合就是黑白配,它是最简单又经典的高级感配色,尤其是在冬天,这样搭配美得优雅又高级。这一期,我们……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网