Promise这一篇就够了

阅读: 评论:0

Promise这一篇就够了

Promise这一篇就够了

参考:

V8 Promise源码全面解读 - 掘金

备份:V8 Promise源码全面解读_lengye7的博客-CSDN博客

上面这篇文章中的几个错误:

1、resolve不是对应于规范的FulfillPromise而是对应于Promise Resolve Functions。

2、reject不是对应于规范的RejectPromise而是对应于Promise Reject Functions。

上面这篇文章之中提到的TriggerPromiseReactions ( reactions, argument  )由标准规定,会给reject添加一个handler,实际上,标准并没有提到。另外ECMAScript并没有规定该抽象操作的具体实现,只是规定了它应该起到的作用。在HTML sepc文档中对此做出了比较明确的解释,那里也规定了具体的实现方式,但是该实现方式并不是添加一个handler,该方式要求将没有handler的rejected状态的promise放入到一个unhandle集合中(about-to-be-notified),然后在事件循环过程中进行计算,通知用户。

(具体计算过程:将该promise移出about-to-be-notified结合,查询该promise的rejection是否已经是handled rejection,如果是,就设置该promise已经是handled rejection状态;如果已经不是handled rejection,则将该promise放入到outstanding rejected promises weak set中。)

对于V8来说,其应该是直接初始化了一个micro task,然后该micro task会计算(上述括号中的过程,V8实现的micro task计算是针对特定关联的promise来计算。),实际上,这个时候对于rejected 状态的promise来说,其并没有一个handler,处于unhandled状态。

当该micro task获得执行的时候,如果该rejected promise仍然不具备一个handler,那么就会在console通知用户,即抛出异常——unhandled rejection。另外,这个时候也会产生UnhandleRejection事件,同时该promise被放入到另外一个集合中(outstanding rejected promises weak set)。

about-to-be-notified:处于unhandled rejection状态即将触发unhandle rejection事件。

outstanding rejected promises weak set:处于unhandled rejecntion状态已经触发unhandle rejection事件。

那么自然还有第三种状态,handled rejection状态和已经触发unhandle rejection事件的promise。这种promise往往是在抛出异常之后,又添加了onRejected函数的promise,它们的unhandled rejection获得处理,当状态转变为此的时候,又会触发rejection handled事件。

上面描述的过程中,各种状态转换只是便于理解,V8是否真的按照这个来实现的,我并不知道。

但是V8确实是通过产生一个micro task来计算对应promise是否具备handler,当这个micro task获得计算的时候,如果promise仍不具备handler,就在console通知用户Unhandled Rejection。

Creating a JavaScript promise from scratch, Part 7: Unhandled rejection tracking - Human Who Codes

备份:Creating a JavaScript promise from scratch, Part 7: Unhandled rejection tracking_lengye7的博客-CSDN博客

《Chrome V8源码》25.最难啃的骨头——Builtin! - 知乎

备份:

V8源码(在线阅读网站):/+/main:v8/src/builtins/promise-reaction-job.tq;l=73?q=PromiseReactionJob&ss=chromium%2Fchromium%2Fsrc

EcmaScript-262  2021标准文档

前言

深入部分看参考内容即可,如果遇到很大困难就不建议看了,干活也用不上。

深入进不去,浅出得不来。

正文

Promise的构造函数

Promise的构造函数有三个分支:

1、Promise只能当做构造函数,不能作为函数调用,否则触TypeError异常。

2、Promise的excutor必须是函数,否则触发TypeError异常。

3、excutor是同步执行的,excotor中参数有两个,这个是JS解释器自动生成,分别是resolve与reject,它们是函数。

Then函数

注意:一个promise对象的then函数可以调用多次,给一个promise对象设置多个回调处理函数。

Then函数其实就只有3个主要分支:

1、pending状态的promise

将onFulfilled和onRejected回调与新创建的一个promise对象作为一个整体,一起绑定到promise对象上,然后返回这个新创建的promise对象。

2、fulfilled状态的promise 

将onFullfilled回调函数与当前promise对象中保存的的结果值(这个结果值一般由resolve产生或者在链式调用中,由上一个promise对象的执行的回调函数的返回值得到。)以及新创建的promise对象作为参数创建一个micro task,然后将该micro task放入到micro task队列中,返回新创建的promise对象。

3、rejected状态的promise

先判断当前promise对象是否已经具备handler(即onRejected处理函数),然后做相应的处理:具备handler,则不做任何处理;如果不具备,则会做相应的处理,将promise从相应的状态转变为rejection handled。

做完上面的事情,会将onRejected函数与promise对象中保存的结果值(这个结果值一般由reject产生或者在链式调用中,由上一个promise对象中执行的回调函数触发异常得到。)以及新创建的promise对象作为参数生成一个micro task,然后将这个micro task放入到micro task队列中,返回新创建的promise对象。

最后,不论是pending、fulfilled还是rejected状态,它们都会在最后将handler设置为true,表明当前promise具备handler了,即onFulfilled或者onRejected处理函数,即使此时这俩函数都为undefined也会将handler设置为true。

即只要调用了then,就说明具备了handler。

而能够返回promise对象,这也是其能够支持链式调用的关键所在,这个返回的promise对象与当前then函数所提供的onFulfilled和onRejected相绑定,会作为micro task的一部分。

实际上,一个micro task其实包含4部分内容:onFulfiled或者onRected处理函数,then函数返回的新创建的promise对象,当前promise对象的执行的结果值,micro task的类型:fulfilled或者rejected。

规范中对此是怎么定义的呢?

规范中将micro task定义为两部分:

Record { [[Job]]: job, [[Realm]]: handlerRealm }.

其中job是真正的micro task的逻辑部分。

handlerRealm可以理解为job运行的运行时上下文。

对于job来说,其正好对应于上面所说的4部分内容:

Reaction:{

Type:fulfilled或者rejected,

Handler:onFulfilled或者onRejcted,

PromiseCapability:这个是一个对象,包含三部分内容:then函数中返回的新创建的promise对象,与这个新创建的promise关联的resolve函数,与这个新创建的promise关联的reject函数。

}

Argument:promise执行的结果值。

另外一些注意点:

如果onFulfilled与onReject这两个参数不是函数,那么对应的处理函数onFulfiled或者onRjected将会是undefined。

这里需要注意,即使此时的onRejected是空的,没有传入实际的回调函数,也并不影响promise状态转变为rejection handled状态。

此时onRejected=undefined,micro task已经放入micro task队列中。

这一点需要注意,后续会有一个地方跟此强相关,那就是链式调用。

这里还有一点比较有意思的东西,那就是fulfilled状态并没有根据handler是否定义做对应的处理,为什么呢?

按照设计来说,rejected往往意味着执行失败,这代表执行可能出错了,为了不遗漏一些错误或者说异常,于是乎JS需要一种机制来追踪rejected状态的promise的执行状况,所以就整出这样一个东西,方便追踪rejected的状态,以确保rejected在没有得到处理的时候,通过某种方式告知用户。

对于一个处于rejected状态的promise,又没有onRejected这个处理函数,那么一段时间后,JS就会抛出unhandle rejection异常。

                    ---------------参考开篇说的TriggerPromiseReactions ( reactions, argument  )

Resolve函数

记:resolve函数直接对应于规范的Promise Resolve Functions,V8中对应于ResolvePromise。

V8中的ResolvePromise省略了规范的前6步。

这里留下一个坑:resolve实际上会产生非常有意思的行为,后续会说到。

坑已填,看后面的Resolve函数补充。

该函数主要作用就是将pending状态的Promise的对象状态转变为fulfilled(只能处理pending状态的promise),并且根据对应的promise的所有的onFulfilled处理函数与resolve中的参数生成micro task,然后把所有的micro task扔到micro task队列中。

注意:如果一个promise没有调用过then、catch、finally,则不具备handler,调用Resolve也就不会产生micro task。

这里有一个很重要的点:

这里对应的onFulfilled处理函数是通过then绑定到对应的promise的,走的是pending分支。

只要调用了then函数,就被认为绑定了onFulfilled和onRejected函数,即具备了handler。

即使then函数中没有传入onFulfilled或者onRejected函数,也认为对应的promise绑定了一个undefined的处理函数,即onFulfilled或者onRejected为undefined重要)。

这个点与后面的链式传输与micro task执行直接相关,有点意思的。

Reject函数

记:reject函数直接对应于规范的Promise Reject Functions,V8中对应于RejectPromise。

V8中的RejectPromise省略了规范的前6步。

该函数主要作用就是将pending状态的Promise的对象状态转变为rejected(只能处理pending状态的promise),并且根据对应promise是否具备handler来进行处理。如果不具备handler(只要调用过一次then,就说明具备了handler),则执行TriggerPromiseReactions ( reactions, argument  )(关于这个处理,看开篇说的。)。如果具备了handler,则会根据promise对象所有的onRejected处理函数与reject中的参数生成对应micro task,并将其放入到micro task队列中去。

注意:如果一个promise没有调用过then、catch、finally,则不具备handler,调用Reject也就不会产生micro task。 

同样的,这里也有一个很重要的点:

这里对应的onRejected处理函数是通过then绑定到对应的promise的,走的是pending分支。

只要调用了then函数,就被认为绑定了onFulfilled和onRejected函数,即具备了handler。

即使then函数中没有传入onFulfilled或者onRejected函数,也认为对应的promise绑定了一个undefined的处理函数,即onFulfilled或者onRejected为undefined重要)。

这个点与后面的链式传输与micro task执行直接相关,有点意思的。

注意:

这里的resolve和reject是excutor的参数中的resolve和reject,而不是solve()和Promise。reject()。

Catch函数

Catch函数可以认为是then函数的简化版,约等于then(undefined,onRejected)。

所以catch函数也会将对应的promise对象设置被具备handler,其最后也会返回一个新的promise对象。

 所以,只要调用了catch就说明promise具备了handler。

能够返回一个新创建的promise对象,也说明其支持链式调用。

Finally函数

Finally函数虽然最后也是通过调用then函数实现,但是它在调用之前还做了一些特殊处理。

这个特殊处理的地方在于,其会把onFinally重新用promise包装一层。

finally实现的Js代码大概如下:

Promise.prototype.finally = function _finally(onFinally) {var promise = this;var constructor = structor;if (isFunction(onFinally)) {thenFinally=function(value){result=onFinally();valueThunk=function(){return value;}solve(result).then(valueThunk);};catchFinally=function(reason){result=onFinally();thrower=function(){throw reason;}solve(result).then(thrower);}//最后调用then函数。return promise.then(thenFinally,catchFinally);}//当onFinally不是一个函数的时候,走这一条分支。return promise.then(onFinally, onFinally);
}

在底层,finally仍然调用了then,所以finally函数也会将对应的promise对象设置为具备handler,其最后也会返回一个新的promise对象。

所以,只要调用了finally就说明promise具备了handler。

能够返回一个新创建的promise对象,说明其支持链式调用。

Finally的这个处理是很有意思的,当然这与EcmaScript-262标准是一致的:

一般我们正常使用finally的情况下,onFinally都是函数,所以这里都会走if分支内部。

这里可以看到,finally最终调用了then函数,并且重新把onFinally包装了一层。

为什么要包这一层呢?而不是直接把onFinally作为参数传入then呢?

看finally的标准介绍,onFinally这个回调不能接受参数,而then函数的两个回调都是有参数的,所以通过一层包装,直接过滤掉了then函数的回调传入的参数。

而在另外一个方面,这个包装函数又会将上一个promise的结果值继续往下传导,也就是说,finally实际上不处理上一个promise的结果值。

这个就很有意思了,这意味着,对于finally来说,正常执行的结果值(fulfilled状态)会继续往下,在链式调用中传导;或者产生的异常(rejected状态)值也会继续往下,在链式调用中传导。

日常使用中,我发现finally不能处理异常,这一下全明白了。

关于finally函数中的then finally function与catch finally function在27.2.5.3.1和27.2.5.3.2中。

链式调用与micro task的执行

先来看一段链式调用的Js代码:

let p0 = new Promise((resolve, reject) => {reject(123)
})
// p0 的状态为 rejectedlet p1 = p0.then(_ => {console.log('p0 onFulfilled')})
// p0 的 onRejected 作为 handler 进入 microtask 队列
// 但是因为 then 没有传递第二个参数
// 所以 onRejected 是 undefined,那么 handler 也是 undefinedlet p2 = p1.then(_ => {console.log('p1 onFulfilled')})
/*
为p1绑定 
PromiseReaction{onFulfilled:_ => {console.log('p1 onFulfilled')}, onRejected:undefined
}
*/
let p3 = p2.then(_ => {console.log('p2 onFulfilled')}, _ => {console.log('p2 onRejected')})
/*
为p2绑定 
PromiseReaction{onFulfilled:_ => {console.log('p2 onFulfilled')}, onRejected:_ => {console.log('p2 onRejected')
}
*/
let p4 = p3.then(_ => {console.log('p3 onFulfilled')}, _ => {console.log('p3 onRejected')})
/*
为p3绑定 
PromiseReaction{onFulfilled:_ => {console.log('p3 onFulfilled')}, onRejected:_ => {console.log('p3 onRejected')
}
*///p2 onRejected
//p3 onFulfilled

这就是典型的链式调用,所谓链式调用就是根据上一个promise的micro task的执行结果,产生下一个promise的onFulfilled或者onRejected处理函数的micro task,直到最后一个promise没有相应的onFulfilled或者onRejected处理函数为止。

在上述例子中,p4没有通过then或catch绑定onFulfiled或者onRejected处理函数,链式调用到P4终止。

也就是说,只有当第一个promise的micro task执行完成之后,后续的promise才会产生micro task,然后继续执行,以此类推。

#micro task的执行

V8中的promise的micro task的执行的核心方法:

MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask 

从该函数进入到:

PromiseRejectReactionJob函数

再进入到:

PromiseReactionJob

promise的micro task执行的核心逻辑都在PromiseReactionJob里。

promise的micro task的执行主要有两大块核心逻辑:

1、如果handler=undefined(前面有提到过,具备handler,但是handler可以是undefined。),那么就直接将当前micro task对应的promise的结果值以及下一个promise作为参数调用FuflfillPromiseReactionJob(当前promise为fulfilled时)或者RejectPromiseReactionJob(当前promise为rejected时)。

2、如果handler(onFulfilled或者onRejected)是一个函数,则会执行handler函数。如果该handler执行过程中没有出现异常,则利用其返回值、下一个promise作为参数值调用FuflfillPromiseReactionJob。如果执行过程中出现了异常,那么就会把异常和下一个promise作为参数调用RejectPromiseReactionJob。

下一个promise是在调用then、catch、finally的时候绑定到上一个promise上的,所以上一个promise的micro task执行的时候,能够得到下一个promise。

看起来micro task的执行逻辑很简单,短短几句话就结束了,实际上并非如此。

真正复杂的部分都在FuflfillPromiseReactionJob与RejectPromiseReactionJob中,这两个调用在下一个部分,链式调用的micro task的产生里做详细的讲解。

micro task执行的时候,它是怎么如何获取到下一个promise的,以及该promise对应的resolve或者reject函数的?

前面在then函数中提到过,一个micro task的组成部分,其中Reaction中包含一个重要的结构,那就是PromiseCpability,该结构中保存了then函数返回的新创建的Promise以及对应的resolvey与reject函数。

当解释器执行micro task的时候,正好能够从该结构中获取到下一个promise以及该promise对应的resolve或者reject函数。

#链式调用的micro task的产生

回顾:

promise的micro task的产生途径:

1、settled状态的promise通过then函数来触发一个fulfilled类型的micro task或者触发一个rejected类型的micro task。

2、绑定了回调函数的promise通过resolve直接产生。

3、绑定了回调函数的promise通过reject直接产生。

promise的micro task组成部分:

1、onFulfilled或者onRejected处理函数。

2、新创建的promise对象以及该promise对象对应的resolve和reject函数。

3、micro task的类型。

4、当前promise的执行结果值。(这个结果值由resolve或rejected产生,后续将看到链式调用中,这个结果值还可以是回调函数执行的返回值。)

那么链式调用中的micro task是如何产生的呢?

FuflfillPromiseReactionJob

该函数的内部逻辑就只有三部分:

1、当传入的promise参数是promise时;

2、当传入的promise参数是undefined时;

3、当传入的promise参数是promise_capability时。

其中,2直接忽略不计(链式调用中不可能出现这情况,啥时候出现我也不知道),1和3其实最后都是调用resolve(当然代码中不是resolve,而是ResolvePromise,实际上调用的函数就是对应于resolve函数,也就是规范中的Promise Resolve Functions)。

所以,该函数也就是相当于直接调用resolve。

而调用resolve又会改变对应的promise状态,如果对应的promise具备一个hanlder又会产生一个onFulfilled的micro task。

RejectPromiseReactionJob

该函数的内部逻辑有两大部分:

1、当micro task的类型是kPromiseReactionFulfill时,会调用PromiseRejectReactionJob。

最后,又会回到RejectPromiseReactionJob中,走第2部分的分支。

所以,这一部分的目的就是为了转换micro task的类型为kPromiseReactionReject。

2、当micro task的类型是 kPromiseReactionReject时,其分为三部分:

1)、当传入的promise参数是promise时;

2)、当传入的promise参数是undefined时;

3)、当传入的promise参数是promise_capability时。

其中,2)直接忽略不计(链式调用中不可能出现这情况,啥时候出现我也不知道),1和3其实最后都是调用reject(当然V8代码中不是reject,而是RejectPromise,实际上调用的函数就是对应于reject函数,也就是规范中的Promise Reject Functions)。

因此该函数实际上也就是相当于直接调用reject。

而调用reject又会改变对应的promise状态,如果对应的promise具备一个handler,又会产生一个onRejected的micro task。

注意点:

V8的实现中,省略了Promise Resolve Functions和Promise Reject Functions的前6步。

一般来说,链式调用的第一个micro task都是通过 在excutor中执行resolve或者reject将promise的状态转变为settled状态,然后再通过then函数产生micro task。

那么链式调用的后续的micro task是如何产生的呢?

以上面那个例子进行说明:

1、P0那一步执行完成时候,此时P0的状态为rejected状态,保存的结果值为123。

2、通过执行let p1 = p0.then(_ => {console.log('p0 onFulfilled')}),于是通过then函数产生了第一个micro task。

3、接下来继续执行同步代码,p1,p2,p3分别通过then函数绑定了onFulfilled和onRejected函数。

4、p0的rejected状态的micro task获得执行,由于p0的onRejected是undefined,所以就把p1和p0的结果值作为参数调用了RejectPromiseReactionJob(micro task的执行中第1点。),又由于p1之前通过then函数调用使自己具备了handler,于是一个onRejected micro task产生了。

5、p1对应的rejected状态的micro task获得执行,因为p1的onRejected是undefined,所以就把p2和p1的结果值作为参数调用RejectPromiseReactionJob,又由于p2之前通过then函数调用使自己具备了handler,于是一个onRejected micro task产生了。

6、p2对应的rejected状态的micro task获得执行,因为p2的onRejected是一个函数,所以这一次执行了onRjected这个回调函数(micro task的执行中的第2点),输出p2 onRejected。onRejected这个回调函数顺利获得执行,然后会将onRejected这个回调函数的返回值(这里的返回值为undefined)以及p3作为参数调用FuflfillPromiseReactionJob,由于p3之前通过then函数调用使自己具备了handler,于是一个onFulfilled micro task产生了。

7、p3对应的fulfilled状态的micro task获得执行,因为p3的onFulfilled是一个函数,所以这一次执行了onFulfilled这个回调函数,输出p3 onFulfilled。onFulfilled这个回调函数顺利得到执行,然后会将onFulfilled的返回值(这里是undefined)以及p4作为参数值调用FuflfillPromiseReactionJob,由于p4没有调用过then函数,所以其不具备handler,这一次没有再产生新的micro task,链式调用终止。

小结:

链式调用中,后续的micro task的产生,其实就2种情况:

handler:Promise对应状态的回调处理函数:onFulfilled或者onRejected。

1、handler为undefined,直接透传上一个Promise对象的结果值以及状态给下一个promise对象,然后根据下一个promise对象是否具备handler产生micro task。

2、handler是一个函数,如果handler执行中没有产生异常,则会将返回值作为参数,然后调用下一个promise的resolve函数。如果handler执行中产生了异常,则会将异常值作为参数,然后调用下一个promise的reject函数。然后在下一个promise对象的resolve和reject函数中,根据下一个promise对象是否具备handler产生micro task。

注意:

这里说的resolve和reject并不是solve()和ject(),而是excutor的参数中resolve和reject。

Resolve函数的补充

Resolve函数根据参数的不同,其执行流程会有所不同(3条分支):

1、参数不是一个具备then的对象,其会把对应的Promise状态转变为fulfilled,并根据Promise是否具备handler来产生micro task。这一点与前面的描述基本一致。

2、参数是一个带then方法的对象,这一点在前面没有提及。

这一条分支会把该resolve函数对应的promise对象的resolve和reject函数作为then方法的参数,创建一个then方法的micro task。

为什么不直接同步执行then方法呢?而是创建一个then方法的micro task?

标准中给出的解释:

当在then方法中使用resolve来设置对应的Promise的状态的时候,需要确保周围的代码都计算完之后,then方法才开始计算。

所以,标准直接要求所有then方法都作为一个micro task运行。

这样设计的目的应该是为了保证Promise嵌套运行的时候,内层的Promise能够得到优先执行,这样的话,写出来的嵌套的Promise程序,其执行结果就更加可预期。

3、参数是resolve函数对应的promise对象,直接抛出TypeError异常。这一条分支,我实在想不到什么条件下触发。Js层面中,Resolve函数被调用要不就是在excutor中,要不就是在micro task中执行handler的时候,使用hanler的返回值作为下一个Promise对象的Resolve的参数。

1)、Resolve在excutor中被调用

这一种情况下,根本无法在excutor中访问到new Promise返回的promise对象,所以无法触发。

2)、链式调用中下一个Promise对象的Resolve在micro task中执行handler的时候,使用handler的返回值作为参数被调用

这一种情况是链式调用,那么如何才能在handler中获取到下一个Promise呢?Js层面似乎无法做到。

因此,对于Resolve函数的使用,应该保证参数不是一个具有then方法的对象。

异常在链式调用中的传递

在# mciro task的执行与# 链式调用的micro task的触发中,我提到,JS对于handler=undefined的情况,JS会直接传递上一个promise的状态以及结果值给下一个promise。

以一个例子来说明:

let p0 = new Promise((resolve, reject) => {throw Error("123")
})
// p0 的状态为 rejectedlet p1 = p0.then(() => {console.log('p0 onFulfilled')})
// p0 的 onRejected 作为 handler 进入 microtask 队列
// 但是因为 then 没有传递第二个参数
// 所以 onRejected 是 undefined,那么 handler 也是 undefinedlet p2 = p1.then(() => {console.log('p1 onFulfilled')})
/*
为p1绑定 
PromiseReaction{onFulfilled:() => {console.log('p1 onFulfilled')}, onRejected:undefined
}
*/
let p3 = p2.then( ()=> {console.log('p2 onFulfilled')}, (err) => {console.log('p2 onRejected');console.log(err);})
/*
为p2绑定 
PromiseReaction{onFulfilled:() => {console.log('p2 onFulfilled')}, onRejected:(err) => {console.log('p2 onRejected');console.log(err);
}
*/

p0通过抛出异常将自己的状态设置为rejected,结果值为异常Error("123")。

由于p0的onRejected为undefined,所以handler为undefined,因此p0的状态与结果值传递给p1。

由于p1的onRjected为undefined,所以handler为undefined,因此p1的状态与结果值传递给p2。

p2的onRejected是一个函数,所以这一次执行onRejected对应的函数,异常得到处理,然后onRejected返回值为undefined。接下来,调用p3的resolve将undefined这个返回值传递p3,并设置p3的状态为fulfilled。

在链式调用中,excutor或者某一个回调处理函数中发生异常,会直接导致相应的promise的状态变为rejected,并且该promise的结果值为该异常,然后根据该promise是否具备hanlder产生micro task。当micro task获得执行的时候,如果handler为undefined,则传递该异常给下一个promise并设置下一个promise为rejected状态。一直这样传递下去,直到异常被处理或者抛出异常到console中。

一句话,异常在链式调用中如果得不到处理,就会一直往下传递,直到被处理为止或者最终抛出这个未处理异常到console。

总结

1、尽量保证resolve函数的参数不要是一个具有then方法的对象。

2、链式调用中,尽量保证handler返回值不是一个具有then方法的对象。(handler是onFulfilled或者onRejected函数)

3、链式调用中,始终在结束的地方添加一个catch,用于处理异常(别指望finally会处理异常,处理异常从来不是finally的功能)。

4、链式调用中,finally只用于链式调用的末尾,给链式调用添加一个处理流程。

5、宁愿将多个处理函数重新使用一个函数包装一遍,也不要使用then添加多个处理函数(then添加多个处理函数会增加维护成本的)。

本文发布于:2024-02-01 15:43:23,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170677340737672.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:这一   就够了   Promise
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23