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的构造函数有三个分支:
1、Promise只能当做构造函数,不能作为函数调用,否则触TypeError异常。
2、Promise的excutor必须是函数,否则触发TypeError异常。
3、excutor是同步执行的,excotor中参数有两个,这个是JS解释器自动生成,分别是resolve与reject,它们是函数。
注意:一个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函数直接对应于规范的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函数直接对应于规范的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函数可以认为是then函数的简化版,约等于then(undefined,onRejected)。
所以catch函数也会将对应的promise对象设置被具备handler,其最后也会返回一个新的promise对象。
所以,只要调用了catch就说明promise具备了handler。
能够返回一个新创建的promise对象,也说明其支持链式调用。
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中。
先来看一段链式调用的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函数根据参数的不同,其执行流程会有所不同(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小时内删除。
留言与评论(共有 0 条评论) |