面试准备-异步

271 阅读38分钟

介绍

首先大家知道JS这门语言最早期主要是为了丰富网页的交互所设计出来的,所以在早期设计者为了避免多线程竞态导致的渲染问题,所以在浏览器中就把JS的执行设计成了单线程。但是随着不断的进步(很多东西的进步)。大家的要求不仅限于同步的网络数据加载,操作,甚至是各个特殊的交互细节。那么在这个场景下产生异步。当然上述场景可能不是一个真正的演化过程。但异步现在确实我们现在前端必不可少的一个工具,那么我们今天就一起来学习一下JS中的异步

使用

根据作者目前所掌握的情况,JS中的异步大部分只有三种表现形式:

Callback

回调函数的方式(大部分以事件监听的监听器为主),比如我们使用XMLHttpRequest网络请求的时候,我们就是通过onreadystatechange挂载的事件监听器,来监听事件readystatechange触发我们的监听器回调函数,进行的异步处理,案例如下:

// 构建实例对象
const xhr = new XMLHttpRequest();
// 挂载回调函数
xhr.onreadystatechange = function(){
    if(xhr.readyState == 4){ 
        // 2开头为成功的状态码,不考虑304的情况。
        if(`${xhr.status}`.startsWith('2')){
           console.log(xhr.responseText)
        } else {
           console.log('网络出现错误请求失败!')
        }
     }
}
// 初始化请求
xhr.open(/* 请求方法 */, /* 请求地址 */ , true)
// 发送!
xhr.send()

当然大家说到Callback,总是会一起谈论到回调地狱的概念,回调地狱通常指的是因为过深或者过多的回调执行,产生的代码可读性急剧下降。

Promise

那么后来在ES6Promise固有对象(标准怪异对象)的出现,解决Callback的问题。那么我们一起根据ES官方文档来看看目前Promise的定义(不过真的好长,过程中可能会省略部分内容)

介绍

Promise 是一个对象,用作延迟(也可能是异步)计算的最终结果的占位符。

任何 Promise 都处于三种互斥状态之一:fulfilled,rejected,pending

  • 如果 p.then(f, r) 将立即作业(Job)排队调用函数 f,那么p的状态是fulfilled
  • 如果 p.then(f, r) 将立即作业(Job)排队调用函数 f,那么p的状态是rejected
  • 如果既没有实现也没有拒绝,则 Promise 处于pending状态。

如果一个 Promise 没有pending,即它要么被履行要么被拒绝,则称被。

如果一个 Promise是 fulfilled,或者它已被“锁定”以匹配另一个 Promise 的状态,则该 Promise 被解决。尝试去resolve或者reject一个已经解决的Promise是无效的。如果承承诺未处理,那么承诺就是unresolved。未解决的承诺始终处于pending。已解决的 Promise 可能处于fulfilled,rejected,pending

Promise的抽象操作

PromiseCapability Record(Promise的能力记录)

PromiseCapability Record 是一个记录值,用于封装 Promise 或类似 Promise 的对象,以及能够解析或拒绝该 Promise 的函数。PromiseCapability RecordsNewPromiseCapability 抽象操作生成。

  • PromiseCapability Record包含下表字段
字段名含义
[[Promise]]对象一个用于Promise的对象(大部分情况就是Promise固有对象本身)
[[Resolve]]函数对象用于解决特定Promise的函数对象
[[Reject]]函数对象用于拒绝特定Promise的函数对象
IfAbruptRejectPromise ( valuecapability )

IfAbruptRejectPromise 是使用 PromiseCapability Record 的一系列算法步骤的简写。以下形式的算法步骤:

1. IfAbruptRejectPromise(valuecapability).

的意思等同于下方:

1. 如果 value 是 abrupt completion, 那么

1.1 调用 ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).

1.2 返回 capability.[[Promise]].

2. 否则如果 value 是一个 Completion Record, 设置 value 去 value.[[Value]].

PromiseReaction Records(Promise的反应记录)

PromiseReaction 是一个记录值,用于存储有关当Promise使用给定值resolvedrejected时应如何反应的信息。PromiseReaction 记录由 PerformPromiseThen 抽象运算创建,并由 NewPromiseReactionJob 返回的抽象闭包使用。

  • PromiseReaction records包含下表字段信息 |Field Name | Value| Meaning| | -------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [[Capability]] | PromiseCapability Record or undefined | 此记录为其提供反应处理程序的承诺的能力情况| | [[Type]]| Fulfill or Reject| 当 [[Type]] 为空时,将使用 [[Handler]] 以允许特定于结算类型的行为。 | | [[Handler]] | a JobCallback Record or empty| 应应用于传入值的函数,其返回值将控制派生Promise的操作。如果 [[Handler]] 为空,则将改用依赖于 [[Type]] 值的函数。|
CreateResolvingFunctions ( promise )

抽象操作 CreateResolvingFunctions 采用参数promise,并返回包含字段 [[Resolve]](函数对象)和 [[Reject]] (函数对象)的记录。调用时,它会执行以下步骤:

  1. alreadyResolved为记录 { [[value]]: false }。
  2. stepsResolvePromise Resolve Functions中定义的算法步骤。
  3. lengthResolvePromise Resolve Functions中函数定义的必填参数的数量。
  4. resolve 成为 CreateBuiltinFunction(stepsResolve, lengthResolve, '', « Promise]], [[AlreadyResolved]] »)(用于构建内置函数,传入算法步骤必填参数,内部插槽等)。
  5. 设置 resolve.[[Promise]] 为 promise.
  6. 设置 resolve.[[AlreadyResolved]] 为 alreadyResolved.
  7. stepsReject 成为 Promise Reject Functions.中定义的算法步骤。
  8. lengthRejectPromise Reject Functions中函数定义的必填参数的数量。
  9. reject be CreateBuiltinFunction(stepsReject, lengthReject, '', « Promise]], [[AlreadyResolved]] »)
  10. 设置 reject.[[Promise]] 为 promise.
  11. 设置 reject.[[AlreadyResolved]] 为 alreadyResolved.
  12. 返回记录 Record { [[Resolve]]: resolve, [[Reject]]: reject }.
Promise Reject Functions

promise reject函数是具有 [[Promise]] 和 [[AlreadyResolved]] 内部插槽的匿名内置函数。当使用参数reason调用承诺拒绝函数时,将执行以下步骤:

  1. 设 F 为活动函数对象(也就是当前运行时上下文的函数内部插槽数据)。
  2. 断言:F 有一个 [[Promise]] 内部插槽,其值为对象。
  3. Promise成为F.[[Promise]]。
  4. alreadyResolved为 F.[[AlreadyResolved]]。
  5. 如果alreadyResolved.[[Value]] 是 true, 返回 undefined.
  6. 设置 alreadyResolved.[[Value]] 为 true.
  7. 执行 RejectPromise(promisereason).
  8. 返回 undefined.
Promise Resolve Functions

Promise Resolve Functions是具有 [[Promise]] 和 [[AlreadyResolved]] 内部插槽的匿名内置函数。 当使用参数resolution调用 promise 解析函数时,将执行以下步骤:

  1. 设 F 为活动函数对象(也就是当前运行时上下文的函数内部插槽数据)。

  2. 断言:F 有一个 [[Promise]] 内部插槽,其值为对象。

  3. promise成为F.[[Promise]]。

  4. alreadyResolved为 F.[[AlreadyResolved]]。

  5. 如果alreadyResolved.[[Value]] 是 true, 返回 undefined.

  6. 设置 alreadyResolved.[[Value]] 为 true.

  7. 如果 SameValue(resolutionpromise)为 true,则

    7.1让 selfResolutionError 成为新创建的 TypeError 对象。

    7.2 执行RejectPromise(promiseselfResolutionError)。

    7.3 返回 undefined

  8. 如果Type(resolution)不是对象,则

    a. 执行FulfillPromise(promiseresolution)。

    b. 返回 undefined

  9. 让 then 为 Completion(Get(resolution, "then"))。

  10. 如果then的值是突然完成,则

    10.1 执行RejectPromise(promiseselfResolutionError)。

    10.2 返回 undefined

  11. 让 thenAction 为 then.[[Value]].。

  12. 如果 IsCallable(thenAction为false,则

    12.1 执行FulfillPromise(promiseresolution)。

    12.2 返回 undefined

  13. thenJobCallback成为HostMakeJobCallback(thenAction)

  14. 让 job 为 NewPromiseResolveThenableJob(promiseresolutionthenJobCallback)。

  15. 执行 HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]])。

  16. 返回 undefined

FulfillPromise ( promisevalue )

抽象操作 FulfillPromise 采用参数promise和 value,并返回unused的参数。调用时,它会执行以下步骤:

  1. 断言:(Assert): 当前数据 promise.[[PromiseState]] 是 pending.

  2. 让 reactions 为 promise.[[PromiseFulfillReactions]].

  3. 设置 promise.[[PromiseResult]] 为 value.

  4. 设置 promise.[[PromiseFulfillReactions]] 为 undefined.

  5. 设置 promise.[[PromiseRejectReactions]] 为 undefined.

  6. 设置 promise.[[PromiseState]] 为 fulfilled.

  7. 执行 TriggerPromiseReactions(reactionsvalue).

  8. Return unused.

NewPromiseCapability ( C )

抽象操作 NewPromiseCapability 采用参数 C 并返回正常完成包含 PromiseCapability Recordthrow completion。它尝试以内置 Promise 构造函数的方式使用 C 作为构造函数,以创建 promise 并提取其resolvereject函数。promise加上resolvereject函数用于初始化新的PromiseCapability Record。调用时,它会执行以下步骤:

  1. 如果 IsConstructor(C)false,则引发 TypeError 异常。
  2. 注意:C 假定为支持 Promise 构造函数的参数约定的构造函数(请参见 27.2.3.1)。
  3. promiseCapability 成为
PromiseCapability Record {
    [[Promise]]: undefined,
    [[Resolve]]: undefined,
    [[Reject]]: undefined
}
  1. executorClosure 成为一个新的抽象闭包,其中包含捕获 promiseCapability 的参数(resolve、reject),并在调用时执行以下步骤:

    a. 如果promiseCapability.[[Resolve]] 不是undefined的,会引发 TypeError 异常。

    b. 如果promiseCapability.[[Reject]]不是undefined的,请引发 TypeError 异常。

    c. 设定promiseCapability.[[Resolve]] 为 resolve

    d. 设置promiseCapability.[[Reject]] 为 reject

    e. 返回undefined

  2. executor 成为 CreateBuiltinFunction(executorClosure, 2, '', « »)

  3. promise成为?Construct(C, « executor »).

  4. 如果IsCallable(promiseCapability.[[Resolve]]) 是 false,引发 TypeError 异常。

  5. 如果 IsCallable(promiseCapability.[[Reject]]) 是 false,引发 TypeError 异常。

  6. 设定promiseCapability.[[Promise]] 为 promise.

  7. 返回promiseCapability

IsPromise ( x )

抽象运算 IsPromise 采用参数 x 并返回一个布尔值。它会检查这个Promise本质上是否为对象。调用时,它会执行以下步骤:

  1. 如果 Type(x) 不是 Object,则返回 false
  2. 如果 x 没有 [[PromiseState]] 内部插槽,则返回 false
  3. 返回 true
RejectPromise ( promisereason )

抽象操作 RejectPromise 采用参数 promisereason,并返回unset的参数。调用时,它会执行以下步骤:

  1. 断言:(Assert): 当前数据 promise.[[PromiseState]] 是 pending.
  2. 设置 reactions 为 promise.[[PromiseRejectReactions]].
  3. 设置 promise.[[PromiseResult]] 为 reason.
  4. 设置 promise.[[PromiseFulfillReactions]] 为 undefined.
  5. 设置 promise.[[PromiseRejectReactions]] 为 undefined.
  6. 设置 promise.[[PromiseState]] 为 rejected.
  7. 如果 promise.[[PromiseIsHandled]] 为 false, 执行 HostPromiseRejectionTracker(promise, "reject").
  8. 执行 TriggerPromiseReactions(reactionsreason).
  9. 返回 unused.
TriggerPromiseReactions ( reactionsargument )

抽象操作 TriggerPromiseReactions 采用reactionsPromiseReaction Records 列表)和argument,并返回返回unused。它为反应中的每条记录排队一个新作业(Job)。每个这样的作业(Job)都处理 PromiseReaction Records的 [[Type]] 和 [[Handler]],如果 [[Handler]] 不为空,则调用它传递给定的参数。如果 [[Handler]] 为空,则行为由 [[Type]] 确定。调用时,它会执行以下步骤:

  1. 循环取出 reaction of reactions, 做以下循环体 1.1 让 job 成为 NewPromiseReactionJob(reactionargument).

    1.2 执行 HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).

  2. 返回 unused.

HostPromiseRejectionTracker ( promiseoperation )

主机定义的抽象操作 HostPromiseRejectionTracker 采用参数 promise(承诺)和操作(“reject”或“handle”),并返回unused的参数。它允许宿主环境跟踪承诺的拒绝响应。

HostPromiseRejectionTracker 的实现必须符合以下要求:

  • 它必须正常完成(即不是中断出错,而是全量执行结束)。

HostPromiseRejectionTracker 的默认实现是返回unused的。

HostPromiseRejectionTrackr 在两种情况下调用: 当承诺在没有任何处理程序的情况下被拒绝时,调用它时,其操作参数设置为“reject”。 当处理程序首次添加到被拒绝的承诺时,调用它时,其操作参数设置为“handle”。 HostPromiseRejectionTracker 的典型实现可能会尝试通知开发人员未处理的拒绝,同时如果此类先前的通知后来被附加的新处理程序失效,还要主动通知他们。

如果操作是“handle”,则实现不应以会干扰垃圾回收的方式保留对 promise 的引用。如果操作为“reject”,则实现可能会引用 promise,因为预计reject将很少见,并且在热代码路径上不会发生。

Promise作业(Promise Jobs)

 NewPromiseReactionJob ( reactionargument )

抽象操作 NewPromiseReactionJob 采用参数反应(PromiseReaction Record)和argument,并返回一个包含字段 [[Job]](作业抽象闭包)和 [[Realm]] (Realm Record 或 null) 的记录。它返回一个新的作业抽象闭包(Job Abstract Closure),该闭包将相应的处理程序应用于传入值,并使用处理程序的返回值来resolve 或者 reject与该处理程序关联的派生承诺。调用时,它会执行以下步骤:

  1. 让 job 成为一个新的作业抽象闭包Abstract Closure,没有参数来捕获reaction 和argument,并在调用时执行以下步骤:

    1.1 让promiseCapability成为reaction.[[Capability]]

    1.2 让type成为reaction.[[Type]].。

    1.3 让handler成为reaction.[[Handler]]。

    1.4 如果handler为空,则

    1.4.1 如果type为“Fulfill”,则让handlerResultNormalCompletion(argument)

    1.4.2 否则:

    1.4.2.1 断言:typereject

    1.4.2.2 让处理程序结果为 ThrowCompletion(argument)

    1.5.否则,让 handlerResultCompletion(HostCallJobCallback(handler, undefined, « argument »))

    1.6 如果 promiseCapabilityundefined,则

    1.6.1 断言:处理程序结果不是突然完成。

    1.6.2 返回empty

    1.7 断言:promiseCapabilityPromiseCapability Record

    1.8 如果handlerResult是突然完成,则

    1.8.1 返回 ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »).

    否则

    返回 ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »).

  2. 让handlerRealm为 null。

  3. 如果reaction.[[Handler]]不为空,则

    3.1 让 getHandlerRealmResultComplete(GetFunctionRealm(reaction.[[处理程序]]。[[回调]]))

    3.2 如果 getHandlerRealmResult 是正常完成记录,则将 handlerRealm 设置为 getHandlerRealmResult.[[value]].

    3.3 否则,将handlerRealm设置为当前“领域记录”。

    3.4 注意:handlerRealm永远不会为空,除非handle未定义。当handler是已吊销的代理并且没有 ECMAScript 代码运行时,handlerRealm 用于创建错误对象。

  4. 返回Record { [[Job]]: job, [[Realm]]: handlerRealm }。

NewPromiseResolveThenableJob ( promiseToResolvethenablethen )

抽象操作 NewPromiseResolveThenableJob 采用参数 promiseToResolvethenablethen返回一个包含字段 [[Job]] (job abstract closure) 和 [[Realm]] (Realm Record) 的记录。调用时,它会执行以下步骤:

  1. job 成为一个新的作业抽象闭包,其中没有参数捕获promiseToResolvethenable, 和 then,并在调用时执行以下步骤:

    1.1 让 resolvingFunctions 成为 CreateResolvingFunctions(promiseToResolve)

    1.2 让 thenCallResultCompletion(HostCallJobCallback(thenthenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »))..

    1.3 如果 thenCallResult 是突然完成的,则

    1.3.1 返回 ?Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »)..

    1.4 返回 ? thenCallResult

  2. getThenRealmResultCompletion(GetFunctionRealm(then.[[Callback]]))。

  3. 如果 getThenRealmResult 是正常完成,声明 thenRealm 设置为 getThenRealmResult.[[Value]]

  4. 否则,让thenRealm成为当前的Realm记录。

  5. 注意: thenRealm 不可能为null. 当 then.[[Callback]] is a 撤回代理 并且 无代码执行时, thenRealm 通常用于创建错误对象。

  6. 返回Record { [[Job]]: job, [[Realm]]: thenRealm }。

Promise 构造函数(The Promise Constructor)

Promise构造函数:

  • 是 %Promise% (内部对象名称,可以在基本类型的固有对象中找到对应).
  • 全局对象global object的属性为Promise 
  • 创建并初始化一个新的 Promise 当调用构造函数时。
  • 不应作为函数调用,并且以这种方式调用时将引发异常.
  • 可以用作类定义的扩展子句中的值。打算extends指定的 Promise 行为的子类构造函数必须包括对 Promise 构造函数的super调用,用来支持 Promise 和 Promise.prototype 内置方法所需的内部状态创建和初始化子类实例。
Promise ( executor )

当使用参数 executor调用 Promise 函数时,将执行以下步骤:

  1. 如果 NewTargetundefined, 抛出一个 TypeError exception(这里是说如果不作为构造函数调用,就抛出TypeError exception)。

  2. 如果 IsCallable(executor)false, 抛出一个 TypeError exception

  3. 让 promise 为 ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »).

  4. 设置 promise.[[PromiseState]]pending.

  5. 设置 promise.[[PromiseFulfillReactions]] 为一个新的空列表.

  6. 设置 promise.[[PromiseRejectReactions]] 为一个新的空列表.

  7. 设置 promise.[[PromiseIsHandled]]false.

  8. 设置 resolvingFunctions be CreateResolvingFunctions(promise).

  9. 设置 completion Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »))

  10. 如果 completion 是突然完成的, 那么

    10.1 执行 ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »)

  11. 返回 promise

promise构造函数的属性(Properties of the Promise Constructor)

promise的构造函数:

  • promise构造函数的隐式原型是%Function.prototype%. 函数原型对象
  • 具有以下属性(由于内容比较多,这里我就不照抄全部了,只给安排allresolve, reject, 原文查看这里):
Promise.all ( iterable )

all 函数返回一个新的promise,当前promise的处理方案是resovle一系列的promise,或者是promise中的第一个reject。这个算法通过迭代来处理一系列的promise

  1. Cthis关键字的值.

  2. promiseCapability 为 ? NewPromiseCapability(C).

  3. promiseResolve Completion(GetPromiseResolve(C)).

  4. IfAbruptRejectPromise(promiseResolve, promiseCapability).

  5. iteratorRecordCompletion(GetIterator(iterable)).

  6. IfAbruptRejectPromise(iteratorRecord, promiseCapability).

  7. result Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)).

  8. 如果结果是突然完成的那么

    a. 如果 iteratorRecord.[[Done]]false, 设置 resultCompletion(IteratorClose(iteratorRecord, result)).

    b. IfAbruptRejectPromise(result, promiseCapability).

  9. 返回 ? result.

GetPromiseResolve ( promiseConstructor )

抽象操作 GetPromiseResolve 接受参数 promiseConstructor(构造函数)并返回包含函数对象的正常完成或抛出完成。它在调用时执行以下步骤:

  1. 让 promiseResolve 为 ? Get(promiseConstructor, "resolve").
  2. 如果 IsCallable(promiseResolve) 是 false, 抛出 TypeError exception.
  3. 返回 promiseResolve.
PerformPromiseAll ( iteratorRecordconstructorresultCapabilitypromiseResolve )

抽象操作 PerformPromiseAll 接受参数 iteratorRecordconstructor(构造函数)resultCapability(PromiseCapability Record)promiseResolve(函数对象),并返回包含 ECMAScript 语言值的正常完成或 throw 完成。它在调用时执行以下步骤:

  1. values 为一个新的空列表.

  2. remainingElementsCountRecord { [[Value]]: 1 }.

  3. index 为 0.

  4. 重复

    a. 让 nextCompletion(IteratorStep(iteratorRecord)).

    b. 如果 nextan abrupt completion, 设置 iteratorRecord.[[Done]]true.

    c. ReturnIfAbrupt(next).

    d. 如果 nextfalse, 那么

     i. 设置 iteratorRecord.[[Done]]true.
    
     ii. 设置 remainingElementsCount.[[Value]] 为 remainingElementsCount.[[Value]] - 1.
    
     iii. 如果 remainingElementsCount.[[Value]]0, 那么
    
         1. 让 valuesArray 为CreateArrayFromList(values).
         
         2. 执行 ? Call(resultCapability.[[Resolve]], undefined, « valuesArray »).
         
     iv. 返回 resultCapability.[[Promise]].
     
    

    e. 让 nextValueCompletion(IteratorValue(next))

    f. 如果 nextValue 是突然中断, 设置 iteratorRecord.[[Done]] 为 true.

    g. ReturnIfAbrupt(nextValue).

    h. 添加 undefinedvalues.

    i. 让 nextPromise 为 ? Call(promiseResolve, constructor, « nextValue »).

    j. 让 steps 为特定的算法步骤 Promise.all Resolve Element Functions.

    k. 让 length 为 Promise.all resolve 元素函数中函数定义的必填参数的数量。.

    l. 让 onFulfilledCreateBuiltinFunction(steps, length, "", « [[AlreadyCalled]], [[Index]], [[Values]], [[Capability]], [[RemainingElements]] »).

    m. 设置 onFulfilled.[[AlreadyCalled]]false.

    n. 设置 onFulfilled.[[Index]]index.

    o. 设置 onFulfilled.[[Values]]values.

    p. 设置 onFulfilled.[[Capability]]resultCapability.

    q. 设置 onFulfilled.[[RemainingElements]]remainingElementsCount.

    r. 设置 remainingElementsCount.[[Value]]remainingElementsCount.[[Value]] + 1.

    s. 执行 ? Invoke(nextPromise, "then", « onFulfilled, resultCapability.[[Reject]] »).

    t. 设置 indexindex + 1.

Promise.all Resolve Element Functions

Promise.all 解析元素函数是一个匿名内置函数,用于解析特定的 Promise.all 元素。每个 Promise.all 解析元素函数都有 [[Index]]、[[Values]]、[[Capability]]、[[RemainingElements]] 和 [[AlreadyCalled]] 内部槽。

当使用参数 x 调用 Promise.all 解析元素函数时,将执行以下步骤:

  1. F 设置为当前的active function object(当前执行上下文中的Function 内部插槽, 但在这里应该指的就是当前的Promise.all).

  2. 如果 F.[[AlreadyCalled]]true, 返回 undefined.

  3. 设置 F.[[AlreadyCalled]]true.

  4. indexF.[[Index]].

  5. valuesF.[[Values]].

  6. promiseCapabilityF.[[Capability]].

  7. remainingElementsCountF.[[RemainingElements]].

  8. 设置 values[index]x.

  9. 设置 remainingElementsCount.[[Value]]remainingElementsCount.[[Value]] - 1.

  10. 如果 remainingElementsCount.[[Value]]0, 那么

    a. 让 valuesArrayCreateArrayFromList(values).

    b. 返回 ? Call(promiseCapability.[[Resolve]], undefined, « valuesArray »).

  11. 返回 undefined.

Promise.reject ( r )

reject 函数返回一个新的 Promise,它被传递的参数拒绝。

  1. C 成为当前 this关键字的值.
  2. promiseCapability 为 ? NewPromiseCapability(C)
  3. 执行 ? Call(promiseCapability.[[Reject]], undefined, « r »).
  4. 返回 promiseCapability.[[Promise]].

reject 函数期望它的 this 值是一个支持 Promise 构造函数的参数约定的构造函数。

Promise.resolve ( x )

resolve 函数返回使用传递的参数解析的新 Promise,或者如果参数是此构造函数生成的 Promise,则返回参数本身。

  1. C 成为当前 this关键字的值.
  2. 如果 Type(C) 不是一个 Object, 抛出 a TypeError exception.
  3. 返回 ? PromiseResolve(C, x).
PromiseResolve ( Cx )

抽象操作 PromiseResolve 接受参数 C(构造函数)和 x(ECMAScript 语言值)并返回包含 ECMAScript 语言值的正常完成或抛出完成。它返回一个用 x 解析的新承诺。它在调用时执行以下步骤:

  1. 如果 IsPromise(x) 是 true, 那么

    a. 让 xConstructor 为 ? Get(x, "constructor").

    b. 如果 SameValue(xConstructor, C) 是 true, 返回 x.

  2. 让 promiseCapability 为 ? NewPromiseCapability(C).

  3. 执行 ? Call(promiseCapability.[[Resolve]], undefined, « x »).

  4. 返回 promiseCapability.[[Promise]].

Promise的prototype对象的属性

Promise 原型对象:

  • 是 %Promise.prototype%.
  • 拥有 [[Prototype]] 内部插槽的值指向 %Object.prototype%.
  • 是一个 普通对象.
  • 不存在 [[PromiseState]] 内部插槽 或者 任何其他promise实例上的内部插槽.

(这里的话我们仅关注then,catch, 其他的大家感兴趣可以自己点击查看

Promise.prototype.catch ( onRejected )
  1. promise 成为当前 this关键字的值.
  2. 返回 ? Invoke(promise, "then", « undefined, onRejected »).
Promise.prototype.then ( onFulfilledonRejected )

当使用参数 onFulfilled 和 onRejected 调用 then 方法时,会执行以下步骤:

  1. promise 成为当前 this关键字的值.
  2. 如果 IsPromise(promise) 是 false, 抛出一个 TypeError exception.
  3. C 为? SpeciesConstructor(promise, %Promise%).
  4. resultCapability 为 ? NewPromiseCapability(C).
  5. 返回 PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
PerformPromiseThen ( promiseonFulfilledonRejected [ , resultCapability ] )

抽象操作 PerformPromiseThen 接受参数 promiseonFulfilledonRejected 以及可选参数 resultCapability(一个 PromiseCapability 记录)并返回一个 ECMAScript 语言值。它使用 onFulfilledonRejected 作为其结算操作对 Promise 执行“then”操作。如果传递了 resultCapability,则通过更新 resultCapability 的 promise 来存储结果。如果未通过,则 PerformPromiseThen 将由规范内部操作调用,其中结果无关紧要。它在调用时执行以下步骤:

  1. 断言: promise 是一个promise对象

  2. 如果 resultCapability 不存在, 那么

    a. 设置 resultCapabilityundefined.

  3. 如果 IsCallable(onFulfilled)false, 那么

    a. 那么 onFulfilledJobCallback 为空.

  4. 否则,

    a. 让 onFulfilledJobCallbackHostMakeJobCallback(onFulfilled)(构建一个异步作业).

  5. 如果 IsCallable(onRejected)false, 那么

    a. 让 onRejectedJobCallback 为空.

  6. 否则,

    a. 让 onRejectedJobCallbackHostMakeJobCallback(onRejected).

  7. fulfillReactionPromiseReaction { [[Capability]]: resultCapability, [[Type]]: Fulfill, [[Handler]]: onFulfilledJobCallback }.

  8. rejectReactionPromiseReaction { [[Capability]]: resultCapability, [[Type]]: Reject, [[Handler]]: onRejectedJobCallback }.

  9. 如果 promise.[[PromiseState]]pending, 那么

    a. 添加 fulfillReaction 作为promise.[[PromiseFulfillReactions]]列表的最后一个元素

    b. 添加 rejectReaction 作为 promise.[[PromiseRejectReactions]] 列表的最后一个元素

  10. 否则 如果 promise.[[PromiseState]]fulfilled, 那么

    a. 让 value 为 promise.[[PromiseResult]].

    b. 让 fulfillJobNewPromiseReactionJob(fulfillReaction, value).

    c. 执行 HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]).

  11. 否则,

    a. 断言: promise.[[PromiseState]]rejected.

    b. 让 reasonpromise.[[PromiseResult]].

    c. 如果 promise.[[PromiseIsHandled]]false, 执行 HostPromiseRejectionTracker(promise, "handle").

    d. 让 rejectJobNewPromiseReactionJob(rejectReaction, reason).

    e. 执行 HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]).

  12. 设置 promise.[[PromiseIsHandled]] 为 true.

  13. 如果 resultCapabilityundefined, 那么

    a. 返回 undefined.

  14. 否则,

    a. 返回 resultCapability.[[Promise]].

Promise的实例对象

Promise 实例是从 Promise 原型对象(内部,%Promise.prototype%)继承属性的普通对象。 Promise 实例最初是使用中描述的内部插槽如下

  • promise实例的内部插槽
内部插槽类型叙述
[[PromiseState]]pending, fulfilled, or rejected控制 promise 如何响应对其 then 方法的传入调用
[[PromiseResult]]一个 ECMAScript language value履行或拒绝承诺的价值(如果有)。仅当 [[PromiseState]] 未挂起时才有意义。
[[PromiseFulfillReactions]]一个 List of PromiseReaction Records当/如果承诺从待处理状态转换到已完成状态时要处理的记录。
[[PromiseRejectReactions]]一个 List of PromiseReaction Records当/如果承诺从挂起状态转换为拒绝状态时要处理的记录。
[[PromiseIsHandled]]一个布尔值指示 promise 是否曾经有过履行或拒绝处理程序;用于未处理的拒绝跟踪。

那么到这里我们就大致了解了一个Promise的构造的抽象过程,整个过程还是相当的复杂的感兴趣的同学可以多看几次,由于我们主要是为了解JS异步才进行的Promise学习,相信大家都在我们学习的过程中发现有一个Job作业的概念,那么根据作业的大致介绍以及了解。 作者认为,作业是ES留给不同宿主对象定义自身异步队列优先级的一个方式,他可以定一个一个作业队列去进行管理。那么这里就让我们来看看浏览器node分别是如何自身的异步队列 或 称之为事件循环机制(event loop)

浏览器-事件循环

代理 (agents)

我们首先回忆一下,我们在函数章节时提过的一个概念就是代理(agent),那么一个代理由一组 ECMAScript 执行上下文、一个执行上下文堆栈、一个正在运行的执行上下文、一个代理记录和一个执行线程组成。除执行线程外,代理的组成部分仅属于该代理。

那么我们回忆完以后呢,我们来看看在浏览器环境下,代理的定义:

从概念上讲,代理概念是一个独立于架构的理想化“线程”,其中运行JavaScript代码。此类代码可能涉及多个可以相互同步访问的全局/领域,因此需要在单个执行线程中运行。

具有相同代理的两个 Window 对象并不表示它们可以直接访问在彼此的领域中创建的所有对象。它们必须是相同的源域;请参阅 IsPlatformObjectSameOrigin。

Web 平台上存在以下类型的代理:

  • 同源窗口代理(Similar-origin window agent)

包含各种 Window 对象,这些对象可以直接访问或使用 document.domain 相互访问。

如果包含代理集群的源键为 true,则所有 Window 对象将是同一源,可以直接相互访问,并且 document.domain 将无操作。

两个源相同的 Window 对象可以位于不同的相似源窗口代理中,例如,如果它们分别位于自己的浏览上下文组中。

  • Dedicated worker agent

包含单个 DedicatedWorkerGlobalScope.

尽管给定的 worklet 可以有多个领域,但每个这样的领域都需要自己的代理,因为每个领域都可以独立地执行代码,并且与其他领域同时执行代码。

只有共享(Shared worker agent)和专用(Dedicated worker agent)的工作代理才允许使用 JavaScript Atomics API 潜在模块。

要创建代理,给定一个 canBlock(boolean):

  1. signifier 成为一个唯一的内部值.
  2. candidateExecution 成为一个新的候选执行(candidate execution).
  3. agent 成为一个新的 agent 他的[[CanBlock]]canBlock, [[Signifier]]signifier, [[CandidateExecution]]candidateExecution, 以及 [[IsLockFree1]], [[IsLockFree2]], 和 [[LittleEndian]] 是默认值.
  4. 设置 agent的 event loop 是一个新的event loop.
  5. 返回 agent.

事件循环 event loop

我们可以看到一个event loop是在一个agent下的,那么不同的agent下就有不同event loop

定义

为了协调事件、用户交互、脚本、渲染、网络等,用户代理必须使用本节中描述的事件循环。每个代理都有一个关联的事件循环,该循环是该代理独有的。

同源窗口代理(Similar-origin window agent)的事件循环称为窗口事件循环(window event loop)。专用工作者代理(Dedicated worker agent)、共享工作者代理(Shared worker agent)或服务工作者代理(Service worker agent)的事件循环称为工作者事件循环(worker event loop)。工作集代理(worklet agent)的事件循环称为工作集事件循环(worklet event loop)。

事件循环不一定对应于实现线程。例如,可以在单个线程中协同调度多个窗口事件循环。

但是,对于分配为 [[CanBlock]] 设置为 true 的各种工作代理,JavaScript 规范确实对它们提出了关于前进进度的要求,这实际上相当于在这些情况下需要专用的每个代理线程。

一个事件循环有一个或多个任务队列。任务队列是一组任务。

任务队列是集合,而不是队列,因为事件循环处理模型从所选队列中获取第一个可运行任务,而不是使第一个任务出队。

微任务队列不是任务队列。

根据任务的源字段,每个任务都被定义为来自特定的任务源。对于每个事件循环,每个任务源都必须与特定的任务队列相关联。

本质上,任务源在标准中用于分离逻辑上不同类型的任务,用户代理可能希望区分这些任务。用户代理使用任务队列来合并给定事件循环中的任务源(这里实际上在用户代理层面,已经给不同的任务进行了分级,通过任务队列)。

例如,一个用户代理可以有一个鼠标和按键事件的任务队列(用户交互任务源与之相关联),而另一个任务队列则与所有其他任务源相关联。然后,使用在事件循环处理模型的初始步骤中授予的自由度,它可以在四分之三的时间内赋予键盘和鼠标事件优先于其他任务,保持界面响应但不会饿死其他任务队列。请注意,在此设置中,处理模型仍然强制用户代理永远不会无序地处理来自任何一个任务源的事件。

  • 每个事件循环都有一个当前正在运行的任务,它要么是一个任务,要么是空的。最初,这是空的。它用于处理重入。
  • 每个事件循环都有一个微任务队列,最初是空的。微任务是指通过微任务算法队列创建的任务的通俗方式。
  • 每个事件循环都有一个执行微任务检查点布尔值,最初为假。它用于防止执行微任务检查点算法的可重入调用。
  • 每个窗口事件循环都有一个 DOMHighResTimeStamp类型的上次渲染机会时间,最初设置为0。
  • 每个窗口事件循环都有一个 DOMHighResTimeStamp类型的最新的懒加载周期的开始时间,最初设置为0。
  • 相同窗口事件循环的窗口,会返回全部的 window 对象(他们来自于一个循环)。

任务队列

  • 给定在要进入队列中的任务来源变量source ,它执行一系列步骤变量steps,可选地给定一个事件循环event loop和一个文档document
  1. 如果 event loop 没有赋值, 设置 event loop 设置为隐式事件循环
  2. 如果 document 没有赋值, 设置 document 为隐式文档.
  3. task 为 一个新的任务.
  4. 设置 taskstepssteps
  5. 设置 tasksourcesource
  6. 设置 taskdocumentdocument
  7. 设置 task脚本评估设置对象为一个空集合
  8. 设置 queue为任务队列,他的任务源来关联于事件循环
  9. 添加任务到队列.
  • 给定在要进入队列中的全局任务的任务源变量source, 以及全局变量global和它执行一系列步骤变量steps
  1. 设置 event loop 等于 全局关联的代理的事件循环.
  2. 设置 document 为 全局相关文档, 如果全局对象是 Window 对象; 不然就是 null.
  3. 基于任务给定的源,事件循环,文档,步骤,进行排队执行任务
  • 给定正在队列的的element任务变量source以及元素element它执行一系列步骤变量steps
  1. 设置 globalelement所关联的全局对象.
  2. 全局人物给订的源,全局,步骤,进行排队执行任务
  • 给定要进入队列中的微任务所执行的步骤变量为steps,可选给定的事件循环变量event loop,和文档变量document

如果 event loop 没有赋值, 设置 event loop 设置为隐式事件循环 如果 document 没有赋值, 设置 document 为隐式文档. 设置 microtask 为一个新任务 设置 microtaskstepssteps. 设置 microtasksource to 为 microtask task source. 设置 microtaskdocumentdocument. 设置 microtask脚本评估设置对象为一个空集合 让microtask在时间循环中的微任务队列进行排队

如果在初始执行期间,它会旋转事件循环,则可以将微任务移动到常规任务队列。这是唯一会参考微任务的源、文档、脚本评估环境设置对象集的情况;它们被执行微任务检查点算法忽略。

排队任务时隐含的事件循环是可以从调用算法的上下文中推断出来的。这通常是明确的,因为大多数规范算法只涉及单个代理(因此是单个事件循环)。例外是涉及或指定跨代理通信的算法(例如,在窗口和工作人员之间);对于这些情况,不得依赖隐含的事件循环概念,并且在对任务或微任务进行排队时,规范必须明确提供事件循环。

当隐含文档作为一任务在事件循环中的定义遵循如下:

  1. 如果事件循环不是窗口事件循环,则返回 null。
  2. 如果任务在元素的上下文中排队,则返回元素的节点文档。
  3. 如果任务正在浏览上下文的上下文中排队,则返回浏览上下文的活动文档。
  4. 如果任务正在由脚本排队或为脚本排队,则返回脚本的设置对象的全局对象的关联文档。
  5. 断言:永远不会到达此步骤,因为前面的条件之一必须为真。

处理模型(Processing model)

总循环

只要事件循环存在,它就必须不断地运行以下步骤:

  1. oldestTasktaskStartTime 为空。

  2. 如果事件循环有一个任务队列,其中至少有一个可运行的任务,那么:

    2.1 让 taskQueue 成为一个这样的任务队列,以实现定义的方式选择。

    请记住,微任务队列不是任务队列,因此在这一步不会选择它。但是,在此步骤中可能会选择与微任务任务源相关联的任务队列。在这种情况下,下一步选择的任务最初是一个微任务,但它作为旋转事件循环的一部分被移动了。

    2.2 将 taskStartTime 设置为unsafe shared current time.

    2.3 将 oldestTask 设置为 taskQueue 中的第一个可运行任务,并将其从 taskQueue 中删除(这一步相当于取出任务)。

    2.4 将事件循环当前正在运行的任务(属性)设置为 oldestTask

    2.5 执行oldestTask任务中的执行步骤

    2.6 将事件循环当前正在运行的任务设置为null

  3. 执行微任务检查点(放在后面介绍微任务检查点的执行过程)

  4. 设置 hasARenderingOpportunity 为 false.

  5. 让 now 为 unsafe shared current time.

  6. 如果oldestTask不为空

    6.1 将top-level browsing contexts(顶级浏览上下文)设置为空集合

    6.2 对于 oldestTask 的脚本评估环境设置对象集的每个环境设置对象设置,将设置的顶级浏览上下文附加到顶级浏览上下文。

    6.3 报告长任务(long tasks),传入taskStartTime,now(任务结束时间),顶级浏览上下文,oldestTask。

  7. 更新渲染:如果这是一个窗口事件循环,那么:

    7.1 令 docs 为相关代理的事件循环为该事件循环的所有 Document 对象,任意排序,但必须满足以下条件:

     7.1.1 任何其浏览上下文的容器文档为 A 的文档 B 必须在列表中的 A 之后列出(也就是AB的容器,A优先渲染)。
    
     7.1.2 如果有两个文档 AB,其浏览上下文都是子浏览上下文,其容器文档是另一个 Document C,则列表中 AB 的顺序必须与它们各自的浏览上下文容器在 C 中的包含阴影的树顺序匹配节点树。
    

    在以下迭代文档的步骤中,必须按照在列表中找到的顺序处理每个文档。

    7.2 渲染时机: 移除所有没有渲染机会的浏览上下文

    如果用户代理当前能够向用户呈现浏览上下文的内容,则浏览上下文具有呈现机会,考虑到硬件刷新率限制和用户代理出于性能原因的限制,但考虑到即使它在视口之外也可以呈现的内容.

    如果其活动文档被渲染阻塞,则浏览上下文没有渲染机会;否则,渲染机会取决于硬件限制,例如显示刷新率和其他因素,例如页面性能或文档的可见性状态是否“可见”。渲染机会通常定期发生。

本规范不强制要求任何特定模型来选择渲染机会。但是例如,如果浏览器试图达到 60Hz 的刷新率,那么渲染机会最多出现在每秒 60 次(大约 16.7 毫秒)。如果浏览器发现浏览上下文无法维持此速率,则该浏览上下文可能会下降到更可持续的每秒 30 次渲染机会,而不是偶尔丢帧。类似地,如果浏览上下文不可见,用户代理可能会决定将该页面降低到每秒 4 次甚至更少的渲染机会。

7.3 如果 docs 不为空,则将 hasARenderingOpportunity 设置为 true,并将此事件循环的最后一次渲染时机时间设置为 taskStartTime

7.4 不必要的渲染:从文档中删除满足以下两个条件的所有 Document 对象:

用户代理认为更新 Document 的浏览上下文的渲染不会有明显的效果,并且
Document 的动画帧回调映射为空。

7.5 从文档中删除用户代理认为由于其他原因导致最好跳过更新呈现的所有 Document 对象。

标记为“渲染机会”的步骤可防止用户代理在无法向用户呈现新内容时更新渲染(没有渲染机会)。 标记为 Unnecessary rendering 的步骤可防止用户代理在没有要绘制的新内容时更新渲染。 此步骤使用户代理能够防止以下步骤由于其他原因运行,例如,确保某些任务在彼此之后立即执行,仅交错微任务检查点(并且没有交错动画帧回调)。具体来说,用户代理可能希望将计时器回调合并在一起,而不需要中间渲染更新。

7.6 对于文档中的每个完全活动的文档,如果其浏览上下文是顶级浏览上下文,则刷新该文档的自动对焦候选。

7.7 对于文档中的每个完全活动的文档,运行该文档的调整大小步骤,现在作为时间戳传入。 [CSSOMVIEW]

7.8 对于文档中的每个完全活动的文档,运行该文档的滚动步骤,现在作为时间戳传入。 [CSSOMVIEW]

7.9 对于文档中的每个完全活动的文档,评估媒体查询并报告该文档的更改,现在作为时间戳传入。 [CSSOMVIEW]

7.10 对于文档中的每个完全活动的文档,更新动画并为该文档发送事件,现在作为时间戳传入。 [网络动画]

7.11 对于文档中的每个完全活动的文档,运行该文档的全屏步骤,现在作为时间戳传入。 [全屏]

7.12 对于文档中的每个完全活动的文档,如果用户代理检测到与 CanvasRenderingContext2D 或 OffscreenCanvasRenderingContext2D 关联的后备存储上下文已丢失,则它必须为每个此类上下文运行上下文丢失步骤:

7.12.1 如果 context 是 CanvasRenderingContext2D,则让 canvas 为 context 的 canvas 属性的值,否则为 context 的关联OffscreenCanvas 对象。
7.12.2 将上下文的上下文丢失设置为`true`。
7.12.3 将渲染上下文重置为给定上下文的默认状态。
7.12.4 让 shouldRestore 成为在画布上触发名为 contextlost 的事件的结果,cancelable 属性初始化为 true7.12.5 如果 shouldRestore 为 false,则中止这些步骤。
7.12.6 尝试通过使用上下文的属性创建后备存储并将它们与上下文相关联来恢复上下文。如果失败,则中止这些步骤。
7.12.7 将上下文的上下文丢失设置为 false7.12.8 在画布上触发一个名为 contextrestored 的事件。

7.13 对于文档中的每个完全活动的文档,运行该文档的动画帧回调,现在作为时间戳传入。

7.14 对于文档中的每个完全活动的文档,运行该文档的更新交叉点观察步骤,现在作为时间戳传入。 [INTERSECTIONOBSERVER]

7.15 为 docs 中的每个 Document 对象调用标记绘制时间算法。

7.16 对于文档中的每个完全活动的文档,更新该文档的呈现或用户界面及其浏览上下文以反映当前状态。

  1. 如果下列条件都为true
  • 这是一个窗口事件循环

  • 此事件循环的任务队列中没有其文档处于完全活动状态的任务

  • 此事件循环的微任务队列为空

  • hasARenderingOpportunity 为假 那么执行:

    8.1 设置事件循环的最后一个空闲周期时间为一个新的unsafe shared current time.

    8.2 让 computeDeadline 为以下步骤:

     8.2.1  让deadline是这个事件循环的最后一个空闲周期开始时间加上50。(未来 50ms 的上限是为了确保在人类感知阈值内对新用户输入的响应能力。)
     8.2.2 让 hasPendingRenders 为假。
     8.2.3 对于此事件循环的相同循环窗口的每个 windowInSameLoop:
         8.2.3.1如果 windowInSameLoop 的动画帧回调映射不为空,或者如果用户代理认为 windowInSameLoop 可能有待处理的渲染更新,请将 hasPendingRenders 设置为 true8.2.3.2 让 timerCallbackEstimates 成为获取 windowInSameLoop 的活动计时器映射值的结果。
          8.2.3.3 对于timerCallbackEstimates的每一个timeoutDeadline,如果timeoutDeadline小于deadline,设置deadline为timeoutDeadline。
     8.2.4 如果 hasPendingRenders 为真,则:
         8.2.4.1 让 nextRenderDeadline 成为此事件循环的最后一次渲染机会时间加上(1000 除以当前刷新率)(刷新率可以是硬件或实现特定的。对于 60Hz 的刷新率,nextRenderDeadline 将在最后一次渲染机会时间之后大约 16.67 毫秒。)。
         8.2.4.2 如果 nextRenderDeadline 小于截止日期,则返回 nextRenderDeadline。
     8.2.5 返回deadline
    

    8.3 对于该事件循环的同循环窗口的每个窗口win,执行win的空闲期算法,步骤如下:返回调用computeDeadline的结果,粗化给定win的相关设置对象的跨域隔离能力。

  1. 如果这是一个工作事件循环,那么:

    9.1 如果此事件循环的代理的单个领域的全局对象是受支持的 DedicatedWorkerGlobalScope 并且用户代理认为此时更新其渲染将受益,则:

    9.1.1 设置 now 为 current high resolution time 给到 DedicatedWorkerGlobalScope.

    9.1.2 运行该专用WorkerGlobalsCope的动画框回调,传递now为时间戳。 9.1.3 更新该dedicated worker的渲染以反映当前状态。

    类似于在窗口事件循环中更新渲染的注释,用户代理可以确定专用工作器中的渲染速率。

    9.2 如果事件循环的任务队列中没有任务并且 WorkerGlobalScope 对象的关闭标志为真,则销毁事件循环,中止这些步骤,继续运行下面 Web worker 中所定义的内容。

微任务检查点
  1. 如果事件循环执行微任务检查点为真,则返回。

  2. 将事件循环的执行微任务检查点设置为 true。

  3. 当事件循环的微任务队列不为空:

    3.1 让 oldestMicrotask 成为从事件循环的微任务队列中出列的结果。

    3.2 设置当前任务循环正在执行的任务为oldestMicrotask

    3.3 执行 oldestMicrotask

    这可能涉及调用脚本回调,最终调用运行脚本步骤后的清理,这称为再次执行微任务检查点算法,这就是我们使用执行微任务检查点标志来避免重入的原因(其实就是防止递归调而造成任务阻塞)。

    3.4 将事件循环的当前正在运行的任务设置回 null。

  4. 对于负责事件循环是此事件循环的每个环境设置对象,通知该环境设置对象上的拒绝承诺。

  5. 清理 Indexed Database transactions事务。

  6. 执行 ClearKeptObjects().

  7. 将事件循环的执行微任务检查点设置为 false

常见的任务源头

以下任务源被本规范和其他规范中的许多大部分不相关的特性使用。

  • DOM操作任务源 此任务源用于对 DOM 操作做出反应的功能,例如在将元素插入文档时以非阻塞方式发生的事情。

  • 用户交互任务源 此任务源用于响应用户交互的功能,例如键盘或鼠标输入。

为响应用户输入而发送的事件(例如点击事件)必须使用与用户交互任务源一起排队的任务来触发。 [UI事件]

  • 网络任务源 此任务源用于响应网络活动而触发的功能。

  • 历史遍历任务源 此任务源用于对 history.back() 和类似 API 的调用进行排队。

总结

那么首先我们知道每个浏览器环境下的算法定义都是JS提供给宿主的接口由代理以及领域定义,浏览器定义了多个不同的代理,其中代理包含了一个事件循环,与我们常见的就是 同源窗口事件代理(Similar-origin window agent)的事件循环。

每个事件循环执行执行都要经历大致以下过程

  1. 任务提取执行
  2. 微任务点检查,执行
  3. 浏览上下文检查,执行
  4. 空闲周期检查执行
  5. worker代理事件循环检查,执行

那么实际项目中,如果大家需要插入任务列表可以使用setTimeout(...)来进行任务的插入,通过queueMicrotask(...)可以用来对微任务队列进行位微任务插入。

那么完成了浏览器-事件循环的认识,我们再来看看node的事件循环

node-事件循环

其实node的官方已经事件循环讲的很清晰了,这里感兴趣的同学可以直接阅读官方文档进行学习,那么我在这里我说说我的个人理解,实际上我们可以把node的事件循环理解为有多个不同的事件循环阶段(timers, pending callbacks, idle, prepare, poll, check, close callback),每个阶段都有一个独立的运行流程,他们掌握自身的任务列表,跟微任务队列,按照先完任务,再执行微任务的方式进行。

  • 官方图片 image.png

其中在微任务队列前,还需要完成process.nextTick的调用,再进入到微任务队列的检查点中。

那么到这里,关于JS的异步的知识大部分就介绍完了,当然JS的异步也包含了generator以及async await但是由于篇幅的问题,我后续会单独拿出来作为一个单独的文章进行学习说明

完美!

下一篇: 面试准备-模块化