«Это третий день моего участия в первом испытании обновлений 2022 года. Подробную информацию о мероприятии см.:Вызов первого обновления 2022 г."
слова, написанные впереди
Что вы получите от прочтения этой статьи?
- Поймите весь процесс исходного кода V8 Promise, больше не будет тем Promise, которые могут заманить вас в ловушку в мире, я так уверен, что эта статья - галантерея.
- Понимать или реализовывать только спецификацию Promise/A+, которая по-прежнему сильно отстает от Promise в JavaScript.
- Если во время интервью вы ответите на Обещание в глубине этой статьи, это должно быть оружием, чтобы получить предложение SP или SSP, потому что интервьюер, вероятно, не знает этого знания.
Вы знаете настоящий браузер и узелPromise
Каков порядок исполнения, если вы только что прочиталиPromise/A+
нормативныйPromise
Поймите, тогда я вам точно скажу, вы правыPromise
Восприятие порядка исполнения неверно. Если вы мне не верите, взгляните на два вопроса ниже.
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4)
}).then(res => {
console.log(res);
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
// 0 1 2 3 4 5 6
new Promise((resolve, reject) => {
Promise.resolve().then(() => {
resolve({
then: (resolve, reject) => resolve(1)
});
Promise.resolve().then(() => console.log(2));
});
}).then(v => console.log(v));
// 2 1
согласно сPromise/A+
Канонически результат, напечатанный приведенным выше кодом, должен быть 0 1 2 4 3 5 6, потому что когдаthen
вернутьPromise
нужно дождаться этогоPromise
После завершения статуса синхронизации и стоимостиthen
результат.
Но когдаV8
даже крупная поддержкаPromise
Результаты выполнения во всех основных браузерах: 0 1 2 3 4 5 6
как они это делают
Promise/A+
Спецификации разные (и не могу сказать то же самое, потому чтоPromise
Не описывая явно логику их выполнения, просто давая некоторые спецификации) и последовательно?
Знать,Promise
принадлежатьJavaScript
частьJavaScript
серединаPromise
Спецификация реализации не является производной отPromise/A+
, но изECMAScript
Технические характеристики.
Итак, чтобы узнать ответ на этот вопрос, мы не можем просто посмотреть наPromise/A+
, для процесса выполнения и последовательности кода наше внимание должно быть сосредоточено наECMAScript
илиV8
начальство.
Далее буду комбинироватьECMAScript
нормаV8
серединаPromise
Полная интерпретация исходного кода.
Заранее нужно сказать еще три вещи:
- Эта статья больше подходит для студентов, у которых есть определенная основа для чтения Promises.Если студенты, которые не понимают Promises, могут сначала прочитать эти статьи
- Для кода C++, опубликованного позже, вам нужно сосредоточиться только на местах с китайскими комментариями.
- Поскольку блок кода не будет переноситься автоматически, рекомендуется читать на стороне ПК для лучшего чтения.
- Статья очень длинная, ее можно собрать, если успеть успокоиться и не спеша прочитать.
- Нравится 👍🏻, нравится 👍🏻, нравится 👍🏻
В заголовок
PromiseState
3 состояния Обещания,pending
,fulfilled
а такжеrejected
,Исходный код выглядит следующим образом:
// Promise constants
extern enum PromiseState extends int31 constexpr 'Promise::PromiseState' {
kPending,// 等待状态
kFulfilled,// 成功状态
kRejected// 失败状态
}
недавно созданныйPromise
вpending
условие. при звонкеresolve
илиreject
После функции,Promise
вfulfilled
илиrejected
статус, послеPromise
остается неизменным, т.Promise
Изменение состояния необратимо при повторном вызовеresolve
илиreject
ничего не случится,Promise
В исходном коде много утверждений, связанных с состоянием, поэтому я не буду вдаваться в подробности.Promise
Все знакомы с тремя состояниями.
JSPromise
Описание JSPromisePromise
основная информация,Исходный код выглядит следующим образом:
bitfield struct JSPromiseFlags extends uint31 {
// Promise 的状态,kPending/kFulfilled/kRejected
status: PromiseState: 2 bit;
// 是否有onFulfilled/onRejected处理函数,
// 没有调用过 then 方法的 Promise 没有处理函数
//(catch方法的本质是then方法,后面会介绍)
has_handler: bool: 1 bit;
handled_hint: bool: 1 bit;
async_task_id: int32: 22 bit;
}
@generateCppClass
extern class JSPromise extends JSObject {
macro Status(): PromiseState {
// 获取 Promise 的状态,返回
// kPending/kFulfilled/kRejected 中的一个
return this.flags.status;
}
macro SetStatus(status: constexpr PromiseState): void {
// 只有 pending 状态的 Promise 才可以被改变状态
assert(this.Status() == PromiseState::kPending);
// Promise 创建成功后,不可将 Promise 设置为 pending 状态
assert(status != PromiseState::kPending);
this.flags.status = status;
}
macro HasHandler(): bool {
// 判断 Promise 是否有处理函数
return this.flags.has_handler;
}
macro SetHasHandler(): void {
this.flags.has_handler = true;
}
// promise 处理函数或结果,可以是:
// 空
// onFulfilled/onRejected构成的链表
// promise的确认值(resolve的参数)
reactions_or_result: Zero|PromiseReaction|JSAny;
flags: SmiTagged<JSPromiseFlags>;
}
когдаPromise
При изменении состояния, например при вызовеresolve/reject
функция,SetStatus
будет вызван метод;Javascript
вызов слояresolve
метод,reactions_or_result
поле будет назначено какresolve
входящие параметры;Javascript
вызов слояthen
метод, указывающий, что функция-обработчик уже существует,SetHasHandler()
будет называться.Status/SetStatus
Один из этих двух методов получаетPromise
состояние, установкаPromise
условие;
разное
- исполнитель: это функция,
Promise
Параметры, полученные конструктором, вызовитеexecutor
Передаваемые параметрыresolve
а такжеreject
. - PromiseReaction: объект, представляющий
Promise
функция-обработчик, потому чтоPromise
несколько вызововthen
Метод будет иметь несколько функций обработки, поэтому базовая структура данных представляет собой связанный список, и каждый узел хранитonFulfilled
а такжеonRejected
функция.
let p = new Promise((resolve, reject) => {
resolve(123)
// 会将 reactions_or_result 设置为 123
// 会调用 SetHasHandler
resolve(234)// 不会发生任何事,相当于没写
reject(234)// 也不会发生任何事,相当于没写
})
Конструктор
КонструкторИсходный код выглядит следующим образом:
PromiseConstructor(
js-implicit context: NativeContext, receiver: JSAny,
newTarget: JSAny)(executor: JSAny): JSAny {
// 1. 如果不存在 new 关键字, throw a TypeError exception.
if (newTarget == Undefined) {
ThrowTypeError(MessageTemplate::kNotAPromise, newTarget);
}
// 2. 如果传入的参数不是一个回调函数, throw a TypeError exception.
if (!Is<Callable>(executor)) {
ThrowTypeError(MessageTemplate::kResolverNotAFunction, executor);
}
let result: JSPromise;
// 构造一个 Promise 对象
result = NewJSPromise();
// 从 Promise 对象身上,获取它的 resolve 和 reject 函数
const funcs = CreatePromiseResolvingFunctions(result, True, context);
const resolve = funcs.resolve;
const reject = funcs.reject;
try {
// 直接同步调用 executor 函数,resolve 和 reject 做为参数
Call(context, UnsafeCast<Callable>(executor), Undefined, resolve, reject);
} catch (e) {
// 如果出现异常则调用 reject 函数
Call(context, reject, Undefined, e);
}
return result;
}
Сначала проанализируйте дваThrowTypeError
, Следующий код может вызвать первыйThrowTypeError
.
Promise() // Uncaught TypeError: undefined is not a promise
Причина в том, что он не используетсяnew
вызов оператораPromise
конструктор, на этот разnewTarget
равныйUndefined
, сработалThrowTypeError(MessageTemplate::kNotAPromise, newTarget)
.
Следующий код запускает второйThrowTypeError
.
new Promise() // Uncaught TypeError: Promise resolver undefined is not a function
В настоящее времяnewTarget
не равноUndefined
, не запускает первыйThrowTypeError
. но звонитPromise
В конструктор не передаются параметрыexecutor
, который запускает второйThrowTypeError
.
executor
Типом является функция, и в мире JavaScript функции обратного вызова обычно вызываются асинхронно, ноexecutor
является синхронным вызовом. существуетCall(context, UnsafeCast(executor), Undefined, resolve, reject)
Эта строка, вызываемая синхронноexecutor
.
console.log('同步执行开始')
new Promise((resolve, reject) => {
resolve()
console.log('executor 同步执行')
})
console.log('同步执行结束')
// 本段代码的打印顺序是:
// 同步执行开始
// executor 同步执行
// 同步执行结束
Аргументы, полученные конструктором Promise
executor
, Являются ли синхронными вызовы
then
PromisePrototypeThen
Promise
изthen
Метод передает две функции обратного вызоваonFulfilled
а такжеonRejected
соответственно для обработкиfulfilled
а такжеrejected
статус и возвращает новыйPromise
.
Слой JavaScriptthen
функция на самом делеV8
серединаPromisePrototypeThen
функция,Исходный код выглядит следующим образом:
PromisePrototypeThen(js-implicit context: NativeContext, receiver: JSAny)(
onFulfilled: JSAny, onRejected: JSAny): JSAny {
const promise = Cast<JSPromise>(receiver) otherwise ThrowTypeError(
MessageTemplate::kIncompatibleMethodReceiver, 'Promise.prototype.then',
receiver);
const promiseFun = UnsafeCast<JSFunction>(
context[NativeContextSlot::PROMISE_FUNCTION_INDEX]);
let resultPromiseOrCapability: JSPromise|PromiseCapability;
let resultPromise: JSAny;
label AllocateAndInit {
// 创建一个新的 promise 用于当做本次 then 的调用结果返回
//(上面有提到then的返回值是一个promise)
const resultJSPromise = NewJSPromise(promise);
resultPromiseOrCapability = resultJSPromise;
resultPromise = resultJSPromise;
}
// onFulfilled 和 onRejected 是 then 接收的两个参数
// 如果不传则默认值为 Undefined
const onFulfilled = CastOrDefault<Callable>(onFulfilled, Undefined);
const onRejected = CastOrDefault<Callable>(onRejected, Undefined);
// 调用 PerformPromiseThenImpl 函数
PerformPromiseThenImpl(
promise, onFulfilled, onRejected, resultPromiseOrCapability);
// 返回一个新的 Promise
return resultPromise;
}
PromisePrototypeThen
функция создает новыйPromise
объект, получитьthen
Два полученных параметра, вызовPerformPromiseThenImpl
Делайте большую часть работы. Здесь есть что отметить,then
Метод возвращает только что созданныйPromise
.
const myPromise2 = new Promise((resolve, reject) => {
resolve('foo')
})
const myPromise3 = myPromise2.then(console.log)
// myPromise2 和 myPromise3 是两个不同的对象
// 有不同的状态和不同的处理函数
console.log(myPromise2 === myPromise3) // 打印 false
Метод then возвращает новое обещание
PerformPromiseThenImpl
PerformPromiseThenImpl
Есть 4 параметра, потому чтоPerformPromiseThenImpl
звонитthen
вызов, поэтому его первые три параметра вызываютсяthen
методPromise
объект и две функции-обработчика для этого объектаonFulfilled
а такжеonRejected
, последний параметр для вызова этогоthen
вернуть новыйPromise
объектresultPromiseOrCapability
.
PerformPromiseThenImpl Исходный код выглядит следующим образом:
transitioning macro PerformPromiseThenImpl(implicit context: Context)(
promise: JSPromise,
onFulfilled: Callable|Undefined,
onRejected: Callable|Undefined,
resultPromiseOrCapability: JSPromise|PromiseCapability|Undefined): void {
if (promise.Status() == PromiseState::kPending) {
// pending 状态的分支
// 如果当前 Promise 还是 pending 状态
// 那么只需要将本次 then 绑定的处理函数存储起来即可
const handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
// 拿到 Promise 的 reactions_or_result 字段
const promiseReactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 考虑一个 Promise 可能会有多个 then 的情况
// reaction 是个链表,每次绑定处理函数都在链表的头部插入
// 存 Promise 的所有处理函数
const reaction = NewPromiseReaction(
handlerContext, promiseReactions, resultPromiseOrCapability,
onFulfilled, onRejected);
// reactions_or_result 可以存 Promise 的处理函数的链表,也可以存
// Promise 的最终结果,因为现在 Promise 处于 pending 状态,
// 所以存的是处理函数 reaction 构成的链表
promise.reactions_or_result = reaction;
} else {
// fulfilled 和 rejected 状态的分支
const reactionsOrResult = promise.reactions_or_result;
let microtask: PromiseReactionJobTask;
let handlerContext: Context;
// fulfilled 分支
if (promise.Status() == PromiseState::kFulfilled) {
handlerContext = ExtractHandlerContext(onFulfilled, onRejected);
// 生成 microtask 任务
microtask = NewPromiseFulfillReactionJobTask(
handlerContext, reactionsOrResult, onFulfilled,
resultPromiseOrCapability);
} else // rejected 分支
deferred {
assert(promise.Status() == PromiseState::kRejected);
handlerContext = ExtractHandlerContext(onRejected, onFulfilled);
// 生成 microtask 任务
microtask = NewPromiseRejectReactionJobTask(
handlerContext, reactionsOrResult, onRejected,
resultPromiseOrCapability);
// 如果当前 promise 还未绑定过处理函数
if (!promise.HasHandler()) {
// 规范中的 HostPromiseRejectionTracker(promise, "reject"),
// 作用是产生一个检测的 microtask 任务,后面会单独介绍。
runtime::PromiseRevokeReject(promise);
}
}
// 即使调用 then 方法时 promise 已经处于 fulfilled 或 rejected 状态,
// then 方法的 onFulfilled 或 onRejected 参数也不会立刻执行,
// 而是进入 microtask 队列后执行
EnqueueMicrotask(handlerContext, microtask);
}
promise.SetHasHandler();
}
ожидающая ветвь функции PerformPromiseThenImpl
PerformPromiseThenImpl имеет три ветви, соответствующие трем состояниям Promise соответственно.Когда метод, вызываемый промисом, находится в состоянии ожидания, он входит в ветвь ожидания. ожидающий вызов филиалаNewPromiseReaction
Функция на основе полученных параметров onFulfilled и onRejected генерируетPromiseReaction
Объект, в котором хранится функция-обработчик промиса и присваиваетсяJSPromise
изreactions_or_result
поле, затем позвонитеpromise.SetHasHandler()
Будуhas_handler
Установить какtrue
(Указывает, что этот объект Promise связал функцию-обработчик)
Рассмотрим случай, когда Promise может вызывать несколько then подряд, например:
const p = new Promise((resolve, reject) => {
setTimeout(_ => {
resolve('my code delay 2000 ms')
}, 2000)
})
p.then(result => {
console.log('第 1 个 then')
})
p.then(result => {
console.log('第 2 个 then')
})
p дважды вызывает метод then, каждый метод then генерируетPromiseReaction
объект. Объект PromiseReaction1 генерируется при первом вызове метода then.В это время значение preactions_or_result
Сохраняется PromiseReaction1.
Объект PromiseReaction2 генерируется, когда метод then вызывается во второй раз, вызываяNewPromiseReaction
функция,PromiseReaction2.next = PromiseReaction1
, PromiseReaction1 становится следующим узлом PromiseReaction2, и, наконец, preactions_or_result
Сохраняется PromiseReaction2. После того, как PromiseReaction2 входит в связанный список функций обработки промисов, он становится головным узлом связанного списка.NewPromiseReaction
функцияИсходный код выглядит следующим образом:
macro NewPromiseReaction(implicit context: Context)(
handlerContext: Context, next: Zero|PromiseReaction,
promiseOrCapability: JSPromise|PromiseCapability|Undefined,
fulfillHandler: Callable|Undefined,
rejectHandler: Callable|Undefined): PromiseReaction {
const nativeContext = LoadNativeContext(handlerContext);
return new PromiseReaction{
map: PromiseReactionMapConstant(),
next: next, // next 字段存的是链表中的下一个节点
reject_handler: rejectHandler,// 失败处理函数
fulfill_handler: fulfillHandler,// 成功处理函数
promise_or_capability: promiseOrCapability,// 产生的新Promise对象
continuation_preserved_embedder_data: nativeContext
[NativeContextSlot::CONTINUATION_PRESERVED_EMBEDDER_DATA_INDEX]
};
}
Когда p находится в состоянии ожидания, общее содержимое поля response_or_result p выглядит так, как показано ниже.
Изображение ниже не является очередью микрозадач, изображение ниже не является очередью микрозадач, а изображение ниже не является очередью микрозадач.
Использование onFulfilled вместо fill_handler на рисунке сделано для удобства понимания, и то же самое верно для onRejected, и он включен только в поля, связанные с текущим содержимым, так что не слишком запутывайтесь.
Выполненная ветвь функции PerformPromiseThenImpl
Логика выполненной ветки намного проще, имея дело с логикой вызова метода then, когда промис находится в состоянии выполнено:
сначала позвониNewPromiseFulfillReactionJobTask
генерироватьmicrotask
,ПотомEnqueueMicrotask(handlerContext, microtask)
будет просто генерироватьmicrotask
положить вmicrotask 队列
, наконец звонитpromise.SetHasHandler()
Будуhas_handler
Установить какtrue
.
new Promise((resolve, reject) => {
resolve()
}).then(result => {
console.log('进入 microtask 队列后执行')
})
console.log('同步执行结束')
// 本段代码的打印顺序是:
// 同步执行结束
// 进入 microtask 队列后执行
Хотя промис уже находится в состоянии выполнено, когда вызывается метод then, функция обратного вызова onFulfilled метода then не будет выполняться немедленно, а войдет в очередь микрозадач для ожидания выполнения.
Отклоненная ветвь функции PerformPromiseThenImpl
Логика отклоненной ветки примерно такая же, как и логика выполненной ветки, но перед тем, как добавить функцию-обработчик onRejected в очередь микрозадач в отклоненной ветке, она сначала определит, есть ли уже у текущего промиса функция-обработчик, и если поэтому он будет вызван первымruntime::PromiseRevokeReject(promise)
, наконец звонитpromise.SetHasHandler()
Будуhas_handler
Установить какtrue
.
if (!promise.HasHandler()) {
runtime::PromiseRevokeReject(promise);
}
здесьruntime::PromiseRevokeReject(promise)
то естьСпецификация ECMAScriptсерединаHostPromiseRejectionTracker(promise, "handle")
,HostPromiseRejectionTracker
является абстрактным методом, что означает, что не существует конкретной логики, определяющей его. Общая цель – обозначитьpromise
уже связанrejected
Функция обработчика состояния. Не удивляйтесь, почему вы хотите это сделать, мы остановимся на этом отдельно позже.
Примечание 1. HostPromiseRejectionTracker вызывается в двух случаях:
Когда обещание отклоняется без какого-либо обработчика, для его параметра действия устанавливается значение «отклонить».
Когда обработчик впервые добавляется к отклоненному промису, он вызывается с параметром действия, установленным на «обработчик».
Привести к --Спецификация ECMAScript(авторский перевод)
резюме
-
Когда промис вызывается методом THEN, создайте новый объект промиса
resultPromise
-
Затем по действующему
promise
Разные состояния для выполнения разной логики- состояние ожидания: будет
then
Две переданные функции обработчика становятся однойPromiseReaction
узел вставляется вpromise.reactions_or_result
голова(PromiseReaction
представляет собой структуру связанного списка), этот шаг заключается в сборе зависимостей, дождитесьpromise
Запускается, когда состояние завершается. - Состояние выполнено: будет создана микрозадача для вызова входящего обработчика onFulfilled с параметром response_or_result в качестве параметра вызова (в данный момент
reactions_or_result
даpromise
значение, то есть вызовresolve
переданные параметрыvalue
) и вставьте его в очередь микрозадач. - статус отклонено: аналогично статусу выполнено, будет создана микрозадача для вызова входящего
onRejected
функция обработчика иreactions_or_result
В качестве аргумента вызова, если для текущего промиса нет обработчика (то есть выполненный промис вызывается в первый раз методом then), он будет помечен как связанныйonRejected
функцию и поместить свою микрозадачу в очередь микрозадач.
- состояние ожидания: будет
-
передача
promise.SetHasHandler()
Поместите обещаниеhas_handler
Установить какtrue
, указывая на то, что вызываемый метод then привязан к функции-обработчику. -
Наконец, верните новый объект Promise.
Давайте подведем итоги
reactions_or_result
3 состояния значения (пустой, связанный список, значение обещания):Когда обещание только что создано, значение response_or_result пусто,
Когда состояние промиса меняется на
fulfilled
/rejected
, его значение соответствуетresolve(value)
/reject(value)
Параметры, переданные в функциюvalue
, то есть,promise
ценность .Когда обещание ожидает выполнения и вызывается
then
назад,reactions_or_result
Для связанного списка каждый элемент связанного списка хранит вызовthen
Передана функция обработчика.
reslove
new Promise((resolve, reject) => {
setTimeout(_ => resolve('fulfilled'), 5000)
}).then(value => {
console.log(value)
}, reason => {
console.log('rejected')
})
Приведенный выше код выполняет функцию разрешения через 5 секунд, и консоль печатает выполнено.
FulfillPromise
reslove(value)
есть в спецификацииFulfillPromise(promise, value)
Функция, ее роль состоит в том, чтобы изменить состояние промиса с отложенного на выполненное, а также превратить все функции обработки этого промиса в микрозадачи и добавить их в очередь микрозадач для выполнения.
Функция разрешения в конечном итоге вызывает функцию FulfillPromise V8,Исходный код выглядит следующим образом:
// https://tc39.es/ecma262/#sec-fulfillpromise
transitioning builtin
FulfillPromise(implicit context: Context)(
promise: JSPromise, value: JSAny): Undefined {
// 断案当前promise状态一定是 pending,因为promise 的状态改变是不可逆的
assert(promise.Status() == PromiseState::kPending);
// 取 Promise 的处理函数,在这之前 Promise 的状态还是 pending
// 所以 reactions_or_result 中存的是 reactions 链表,
// reactions 节点中存储的是里函数
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// Promise 需要修改为 fulfilled 状态,所以 reactions_or_result 存储的
// 不再是处理函数,而是 Promise 的结果,也就是调用 resolve 时传入的参数
promise.reactions_or_result = value;
// 设置 Promise 的状态为 fulfilled
promise.SetStatus(PromiseState::kFulfilled);
// Promise 的处理函数,Promise 的结果都拿到了,开始正式处理
TriggerPromiseReactions(reactions, value, kPromiseReactionFulfill);
return Undefined;
}
FulfillPromise
Логика состоит в том, чтобы заставить функцию-обработчик Promisereactions
,reactions
ТипPromiseReaction
, представляет собой связанный список, учащиеся, которые забыли, могут вернуться к изображению связанного списка выше; установить promise
изreactions_or_result
дляvalue
,этоvalue
передается со слоя JavaScript наresolve
параметр; вызовpromise.SetStatus(PromiseState::kFulfilled)
настраиватьpromise
статусfulfilled
, наконец звонитTriggerPromiseReactions
будущееreactions
Функция-обработчик добавлена в очередь микрозадач.
TriggerPromiseReactions
Исходный код выглядит следующим образом:
// https://tc39.es/ecma262/#sec-triggerpromisereactions
transitioning macro TriggerPromiseReactions(implicit context: Context)(
reactions: Zero|PromiseReaction, argument: JSAny,
reactionType: constexpr PromiseReactionType): void {
// We need to reverse the {reactions} here, since we record them on the
// JSPromise in the reverse order.
let current = reactions;
let reversed: Zero|PromiseReaction = kZero;
// 链表反转
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
currentReaction.next = reversed;
reversed = currentReaction;
}
}
}
current = reversed;
// 链表反转后,调用 MorphAndEnqueuePromiseReaction
// 把链接中的每一项都进入 microtask 队列
while (true) {
typeswitch (current) {
case (Zero): {
break;
}
case (currentReaction: PromiseReaction): {
current = currentReaction.next;
MorphAndEnqueuePromiseReaction(currentReaction, argument, reactionType);
}
}
}
}
TriggerPromiseReactions
сделал две вещи:
- обратный
reactions
Связанный список, реализация метода then была проанализирована ранее, и параметры метода then наконец сохранены в связанном списке. Последний вызываемый метод then, параметры, которые он получает, будут располагаться в начале связанного списка после переноса, что не соответствует спецификации, поэтому его необходимо перевернуть - траверс
reactions
объект, вызовите MorphAndEnqueuePromiseReaction, чтобы поместить каждый элемент в очередь микрозадач
MorphAndEnqueuePromiseReaction
MorphAndEnqueuePromiseReactionПреобразовать PromiseReaction в микрозадачу, и наконец вставить в очередь микрозадачи, сам morph имеет значение трансформации/трансформации, например Polymorphism (полиморфизм).
MorphAndEnqueuePromiseReaction получает 3 параметра, PromiseReaction — это вышеупомянутый объект связанного списка, который является оболочкой для функции обработки промиса, аргумент — это параметр разрешения/отклонения, responseType представляет конечное состояние промиса, значение, соответствующее выполненному состоянию, — это kPromiseReactionFulfill, а значение соответствующее отклоненному состоянию — kPromiseReactionReject.
Логика MorphAndEnqueuePromiseReaction очень проста, потому что конечное состояние промиса к этому моменту уже известно, поэтому объект promiseReactionJobTask можно получить из объекта promiseReaction.Именование переменных promiseReactionJobTask находится в той же строке, что и описание ECMAScript. спецификации, которая на самом деле является легендарной микрозадачей. Исходный код MorphAndEnqueuePromiseReaction выглядит следующим образом, сохраняется только содержимое, относящееся к этому разделу.
transitioning macro MorphAndEnqueuePromiseReaction(implicit context: Context)(
promiseReaction: PromiseReaction, argument: JSAny,
reactionType: constexpr PromiseReactionType): void {
let primaryHandler: Callable|Undefined;
let secondaryHandler: Callable|Undefined;
// 根据不同的 Promise 状态选取不同的回调执行
if constexpr (reactionType == kPromiseReactionFulfill) {
primaryHandler = promiseReaction.fulfill_handler;
secondaryHandler = promiseReaction.reject_handler;
} else {
primaryHandler = promiseReaction.reject_handler;
secondaryHandler = promiseReaction.fulfill_handler;
}
const handlerContext: Context =
ExtractHandlerContext(primaryHandler, secondaryHandler);
if constexpr (reactionType == kPromiseReactionFulfill) {// fulfilled 分支
* UnsafeConstCast(& promiseReaction.map) =
PromiseFulfillReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseFulfillReactionJobTask>(promiseReaction);
// argument 是 reject 的参数
promiseReactionJobTask.argument = argument;
// handler 是 JS 层面 then 方法的第二个参数,或 catch 方法的参数
promiseReactionJobTask.context = handlerContext;
// promiseReactionJobTask 就是那个工作中经常被反复提起的 microtask
// EnqueueMicrotask 将 microtask 插入 microtask 队列
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
// 删除
} else {// rejected 分支
// 逻辑与 fulfilled 分支前面一致
* UnsafeConstCast(& promiseReaction.map) =
PromiseRejectReactionJobTaskMapConstant();
const promiseReactionJobTask =
UnsafeCast<PromiseRejectReactionJobTask>(promiseReaction);
promiseReactionJobTask.argument = argument;
promiseReactionJobTask.context = handlerContext;
promiseReactionJobTask.handler = primaryHandler;
EnqueueMicrotask(handlerContext, promiseReactionJobTask);
}
}
Функция MorphAndEnqueuePromiseReaction очень проста: выбрать onFulfilled или onRejected в зависимости от состояния Promise и поместить его в очередь микрозадач для подготовки к выполнению. Вот выполненная ветка, поэтому выбрано onFulfilled.
const myPromise4 = new Promise((resolve, reject) => {
setTimeout(_ => {
resolve('my code delay 1000')
}, 1000)
})
myPromise4.then(result => {
console.log('第 1 个 then')
})
myPromise4.then(result => {
console.log('第 2 个 then')
})
// 打印顺序:
// 第 1 个 then
// 第 2 个 then
// 如果把 TriggerPromiseReactions 中链表反转的代码注释掉,打印顺序为
// 第 2 个 then
// 第 1 个 then
резюме
Resolve будет обрабатывать только промисы со статусом pending и разрешать промисы.reactions_or_result
настроен на входящийvalue
, который используется как значение промиса и изменяет состояние промиса на выполненное.
Поскольку промис хранит самые последние зависимости в начале связанного списка при использовании then для сбора зависимостей, необходимо сначала перевернуть связанный список, а затем поместить их в очередь микрозадач одну за другой для выполнения.
Основная задача разрешения состоит в том, чтобы обойти зависимости, собранные при вызове метода then в предыдущем разделе, и поместить их в очередь микрозадач для выполнения.
reject
отклонить и разрешить не сильно отличаются
new Promise((resolve, reject) => {
setTimeout(_ => reject('rejected'), 5000)
}).then(_ => {
console.log('fulfilled')
}, reason => {
console.log(reason)
})
Приведенный выше код выполняет функцию отклонения через 5 секунд, и консоль печатает отклонено.
RejectPromise
reject(season)
Функция вызывает V8RejectPromise(promise, season)
функция,Исходный код выглядит следующим образом:
// https://tc39.es/ecma262/#sec-rejectpromise
transitioning builtin
RejectPromise(implicit context: Context)(
promise: JSPromise, reason: JSAny, debugEvent: Boolean): JSAny {
// 如果当前 Promise 没有绑定处理函数,
// 则会调用 runtime::RejectPromise
if (IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate() ||
!promise.HasHandler()) {
return runtime::RejectPromise(promise, reason, debugEvent);
}
// 取出 Promise 的处理对象 PromiseReaction
const reactions =
UnsafeCast<(Zero | PromiseReaction)>(promise.reactions_or_result);
// 这里的 reason 就是 reject 函数的参数
promise.reactions_or_result = reason;
// 设置 Promise 的状态为 rejected
promise.SetStatus(PromiseState::kRejected);
// 将 Promise 的处理函数都添加到 microtask 队列
TriggerPromiseReactions(reactions, reason, kPromiseReactionReject);
return Undefined;
}
HostPromiseRejectionTracker
По сравнению с ReslovePromise, у RejectPromise есть еще одно суждение, позволяющее определить, привязан ли Promsie к функции-обработчику: если функция-обработчик не привязана, она будет выполнена первой.runtime::RejectPromise(promise, reason, debugEvent)
, который на самом деле находится в спецификации ECMAScriptHostPromiseRejectionTracker(promise, "reject")
, это второй раз упоминаетсяHostPromiseRejectionTracker
.
существуетОтклоненная ветвь функции PerformPromiseThenImplупомянут один раз.
В спецификации ECMAScript HostPromiseRejectionTracker — это абстрактный метод, и у него даже нет четкого процесса выполнения, но в спецификации описана его роль.
HostPromiseRejectionTracker используется для отслеживания отклонений Promise, таких как глобальныеrejectionHandled
События реализуются им.
Примечание 1 HostPromiseRejectionTracker вызывается в двух случаях:
Когда обещание отклоняется без какого-либо обработчика, оно вызывается, и второй аргумент передается «отклонить».
Когда обработчик связывается в первый раз с промисом в отклоненном состоянии, он вызывается, и второму параметру передается «дескриптор».
Так вот, при прохождении“handle”
В отличие от маркировки этого объекта обещания как связавшись функцией обработчика, когда прошло“reject”
Нет обработчика для этого объекта Promise относительно метки.
Давайте сначала посмотрим на несколько фрагментов кода, чтобы увидеть, что он на самом деле делает.
JavaScript выдает ошибку, когда мы вызываем Promise с состоянием отклонения и с ним не связан обработчик onRejected.
const myPromise1 = new Promise((resolve, reject) => {
reject()
})
// 报错
И определение того, является ли обработчик привязки асинхронным процессом
console.log(1);
const myPromise1 = new Promise((resolve, reject) => {
reject()
})
console.log(2);
// 1
// 2
// 报错
Мы можем привязать к нему обработчик onRejected, чтобы решить нашу ошибку.
const myPromise1 = new Promise((resolve, reject) => {
reject()
})// 得到一个 rejected 状态的 Promise
myPromise1.then(undefined, console.log)
Вам должно быть интересно, когда и как Promise определяет, привязан ли он к обработчику onRejected?
Это роль HostpromisrejectionTracker, который также упоминается в спецификации ECMAScript при вызовеHostPromiseRejectionTracker(promise, 'reject')
, если для обещания не существует функции-обработчика, для него будет установлена функция-обработчик.
Возвращаясь к логике выше, когда вызывается функция отклонения промиса, если нет обработчика onRejected, он будет вызыватьсяruntime::RejectPromise
добавить в него функцию-обработчик, которая потом будет вызыватьсяTriggerPromiseReactions
Добавьте этот обработчик в очередь микрозадачи, и этот обработчик снова проверяет, привязано ли обещание к новому onRejected (то есть выполнялось ли оно в течение этого периода).HostPromiseRejectionTracker(promise, 'handle')
), выдает ошибку, если ее нет, и ничего не делает, если она есть.
Поэтому при вызове метода then для обещания с состоянием отклонения вам нужно вызвать егоruntime::PromiseRevokeReject(promise)
чтобы указать, что это обещание связано с новым onRejected, чтобы предотвратить появление ошибок.
Таким образом, вы должны связать функцию обработчика до того, как обнаруженная микрозадача будет выполнена, чтобы предотвратить появление этой ошибки.
const myPromise1 = new Promise((resolve, reject) => {
// 同步执行
reject()
// 会向 microtask 队列中插入一个检查 myPromise1
// 是否绑定了新的 onRejected 处理函数的 microtask
})
// macrotask
setTimeout(() => {
// 此时 microtask 已经执行,错误已经抛出,来不及了
myPromise1.then(undefined, console.log)
}, 0)
резюме
Логика отклонения и разрешения в основном одинакова, разделенная на 4 шага:
- Установите причину обещания, которая является параметром отклонения
- Установите состояние промиса: отклонено
- Если промис не имеет обработчика onRejected, он добавит обработчик, который снова проверяет, привязан ли промис к onRejected.
- Из зависимостей, собранных при вызове метода then/catch, то есть объекта promiseReaction, получить микрозадачи одну за другой и, наконец, вставить микрозадачу в очередь микрозадач.
catch
new Promise((resolve, reject) => {
setTimeout(reject, 2000)
}).catch(_ => {
console.log('rejected')
})
PromisePrototypeCatch
Взяв приведенный выше код в качестве примера, когда метод catch выполняется, V8PromisePrototypeCatchметод, исходный код выглядит следующим образом:
transitioning javascript builtin
PromisePrototypeCatch(
js-implicit context: Context, receiver: JSAny)(onRejected: JSAny): JSAny {
const nativeContext = LoadNativeContext(context);
return UnsafeCast<JSAny>(
InvokeThen(nativeContext, receiver, Undefined, onRejected));
}
Исходный код PromisePrototypeCatch на самом деле содержит только эти несколько строк, не что иное, как вызов метода InvokeThen.
InvokeThen
Как следует из названия, InvokeThen вызывает метод then класса Promise,InvokeThenИсходный код выглядит следующим образом:
transitioning
macro InvokeThen<F: type>(implicit context: Context)(
nativeContext: NativeContext, receiver: JSAny, arg1: JSAny, arg2: JSAny,
callFunctor: F): JSAny {
if (!Is<Smi>(receiver) &&
IsPromiseThenLookupChainIntact(
nativeContext, UnsafeCast<HeapObject>(receiver).map)) {
const then =
UnsafeCast<JSAny>(nativeContext[NativeContextSlot::PROMISE_THEN_INDEX]);
// 重点在下面一行,调用 then 方法并返回,两个分支都一样
return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
} else
deferred {
const then = UnsafeCast<JSAny>(GetProperty(receiver, kThenString));
// 重点在下面一行,调用 then 方法并返回,两个分支都一样
return callFunctor.Call(nativeContext, then, receiver, arg1, arg2);
}
}
Метод InvokeThen имеет две ветви, if/else. Логика этих двух ветвей аналогична. Пример кода JS в этом разделе использует ветвь if. Сначала получите собственный метод V8, а затем передайтеcallFunctor.Call(nativeContext, then, receiver, arg1, arg2)
Вызовите метод then. Метод then был представлен ранее и не будет повторяться здесь.
Поскольку затем поймать метод базового вызова метода, то метод catch имеет тот же метод, а затем возвращаемое значение, метод может продолжать ловить исключение, цепочка может продолжать вызывать.
new Promise((resolve, reject) => {
setTimeout(reject, 2000)
}).catch(_ => {
throw 'rejected'
}).catch(_ => {
console.log('last catch')
})
Второй улов приведенного выше кода перехватывает исключение, созданное первым уловом, и, наконец, печатает последний улов.
резюме
Метод catch реализуется путем вызова метода then внизу. Если obj является объектом Promise, obj.catch(onRejected) на уровне JS эквивалентен obj.then(undefined, onRejected)
Сцепленные вызовы then и очереди микрозадач
Promise.resolve('123')
.then(() => {throw new Error('456')})
.then(_ => {
console.log('shouldnot be here')
})
.catch((e) => console.log(e))
.then((data) => console.log(data));
После запуска приведенного выше кода он печатает Error: 456 и undefined. Для удобства описания написание последовательного вызова then заменено на многословное написание.
const p0 = Promise.resolve('123')
const p1 = p0.then(() => {throw new Error('456')})
const p2 = p1.then(_ => {
console.log('shouldnot be here')
})
const p3 = p2.catch((e) => console.log(e))
const p4 = p3.then((data) => console.log(data));
Метод then возвращает новое обещание, поэтому пять обещаний p0, p1, p2, p3 и p4 не равны друг другу.
Когда обещание находится в состоянии отклонения, если обработчик onRejected не может быть найден, состояние отклонения и его значение будут передаваться вниз до тех пор, пока оно не будет найдено. (То же самое верно и для разрешения), этот процесс будет представлен позже.
Роль метода catch заключается в привязке функции onRejected.
Выполнение микрозадачи
После выполнения всех кодов синхронизации начинается выполнение микрозадачи в очереди микрозадач.MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask, Так как есть много типов микрозадач, есть много ответвлений RunSingleMicrotask. Код здесь не указан.
PromiseReactionJob
Во время выполнения микрозадачи MicrotaskQueueBuiltinsAssembler::RunSingleMicrotask вызоветPromiseReactionJob, исходный код выглядит следующим образом:
transitioning
macro PromiseReactionJob(
context: Context, argument: JSAny, handler: Callable|Undefined,
promiseOrCapability: JSPromise|PromiseCapability|Undefined,
reactionType: constexpr PromiseReactionType): JSAny {
if (handler == Undefined) {
// 没有处理函数的 case,透传上一个 Promise 的 argument
if constexpr (reactionType == kPromiseReactionFulfill) {
// 基本类同 JS 层的 resolve
return FuflfillPromiseReactionJob(
context, promiseOrCapability, argument, reactionType);
} else {
// 基本类同 JS 层的 reject
return RejectPromiseReactionJob(
context, promiseOrCapability, argument, reactionType);
}
} else {
try {
// 试图调用 Promise 处理函数,相当于 handler(argument)
const result =
Call(context, UnsafeCast<Callable>(handler), Undefined, argument);
// 基本类同 JS 层的 resolve
return FuflfillPromiseReactionJob(
context, promiseOrCapability, result, reactionType);
} catch (e) {
// 基本类同 JS 层的 reject,当执行 handler 是抛出异常会触发
return RejectPromiseReactionJob(
context, promiseOrCapability, e, reactionType);
}
}
}
PromiseReactionJob определит, есть ли функция обработки, которую необходимо выполнить в текущей задаче, если нет, напрямую вызовет FuflfillPromiseReactionJob со значением предыдущего промиса в качестве параметра, если он существует, выполнит эту функцию обработки, и вызовет FuflfillPromiseReactionJob с параметром результат выполнения в качестве параметра.
То есть, пока промис onFulfilled или onRejected не выдает исключение во время выполнения, промис выполнит FuflfillPromiseReactionJob и изменит состояние на выполненное. RejectPromiseReactionJob выполняется, если возникает исключение.
let p0 = new Promise((resolve, reject) => {
reject(123)
})
// p1 的状态为 reject
let p1 = p0.then(value => {
console.log(value);
}, reason => {
console.log(reason);
return 2
})
// 将 reason => {console.log(reason)} 加入 microtask 队列
p1.then(_ => {
console.log('p1');
})
// 为 p1 添加 PromiseReaction
// 取 microtask 队列 第一个执行,
// handler 为 reason => {console.log(reason)},
// 成功执行 handler, 所以调用 FuflfillPromiseReactionJob
// 执行 p1 的 resolve
Примечание: FuflfillPromiseReactionJob делает много вещей, и выполнение решения — это только одна из его ветвей.
Давайте посмотрим, что именно делает FuflfillPromiseReactionJob.
FuflfillPromiseReactionJob
Исходный код выглядит следующим образом:
transitioning
macro FuflfillPromiseReactionJob(
context: Context,
promiseOrCapability: JSPromise|PromiseCapability|Undefined, result: JSAny,
reactionType: constexpr PromiseReactionType): JSAny {
typeswitch (promiseOrCapability) {
case (promise: JSPromise): {
// 调用 ResolvePromise,也就是 promise 的 resolve(result)
return ResolvePromise(context, promise, result);
}
case (Undefined): {
return Undefined;
}
case (capability: PromiseCapability): {
const resolve = UnsafeCast<Callable>(capability.resolve);
try {
return Call(context, resolve, Undefined, result);
} catch (e) {
return RejectPromiseReactionJob(
context, promiseOrCapability, e, reactionType);
}
}
}
}
FuflfillPromiseReactionJob имеет 3 ветки, вот первая ветка, вызывающаяResolvePromise, этот метод очень важен, он есть в спецификацииPromise Resolve Functions, его роль состоит в том, чтобы синхронизировать результат (значение и состояние) текущей функции-обработчика с создаваемым ею обещанием. обещание или возможность.
В приведенном выше примере promiseOrCapability равен p1, значение равно 2.
ResolvePromise
Это очень важный метод. По сути, он будет вызываться каждый раз, когда нужно выполнить состояние Promise. Его логика также создает множество функций, недоступных в PromiseA+. Ниже кода я удалил неважную часть
// https://tc39.es/ecma262/#sec-promise-resolve-functions
transitioning builtin
ResolvePromise(implicit context: Context)(
promise: JSPromise, resolution: JSAny): JSAny {
// 删
let then: Object = Undefined;
try {
// 调用 FulfillPromise
const heapResolution = UnsafeCast<HeapObject>(resolution);
const resolutionMap = heapResolution.map;
if (!IsJSReceiverMap(resolutionMap)) {
return FulfillPromise(promise, resolution);
}
// 删
const promisePrototype =
*NativeContextSlot(ContextSlot::PROMISE_PROTOTYPE_INDEX);
// 重要:如果 resolution 是一个 Promise 对象
if (resolutionMap.prototype == promisePrototype) {
then = *NativeContextSlot(ContextSlot::PROMISE_THEN_INDEX);
static_assert(nativeContext == LoadNativeContext(context));
goto Enqueue;
}
goto Slow;
} label Slow deferred {
// 如果 resolution 是一个包含then属性的对象,会来到这
try {
// 获取 then 属性
then = GetProperty(resolution, kThenString);
} catch (e) {
return RejectPromise(promise, e, False);
}
// 如果 then 属性不是一个可执行的方法
if (!Is<Callable>(then)) {
// 将执行结果同步到 promise
return FulfillPromise(promise, resolution);
}
goto Enqueue;
} label Enqueue {
// 重要:如果 执行结果是一个 Promise 对象
// 或者包含可执行的 then 方法的对象,会来到这
const task = NewPromiseResolveThenableJobTask(
promise, UnsafeCast<JSReceiver>(resolution),
UnsafeCast<Callable>(then));
return EnqueueMicrotask(task.context, task);
}
}
В методе ResolvePromise есть несколько важных логических операций. Одна из них заключается в вызове FulfillPromise, который был введен в методе разрешения. Функция состоит в том, чтобы изменить состояние промиса на выполненное и установить для него значение, а затем отправить функцию обработки объекта. обещание в очередь микрозадач.
let p0 = Promise.resolve()
let p1 = p0.then(() => {
return 1;
})
p1.then(console.log)
// p0 then 中 onFulfilled 回调进入队列
// PromiseReactionJob 中调用 p0 的 onFulfilled ,得到结果为1
// 调用 FuflfillPromiseReactionJob ,然后调用 ResolvePromise
// ResolvePromise 做如下操作
// 将 p1 变成 fulfilled, 并将 p1 的处理函数 console.log 加到队列,参数为 1
// p1 的 onFulfilled 出队列执行,输出 1
Другая ситуация таковаКогда значение разрешения является объектом Promise или объектом, содержащим метод then. Он вызовет NewPromiseResolveThenableJobTask для создания микрозадачи и добавления ее в очередь микрозадач.
let p0 = Promise.resolve()
// 两种特殊情况
let p1 = p0.then(() => {
return Promise.resolve(1);// 返回值是 Promise 对象
})
let p2 = p0.then(() => {
return {then(resolve, reject){resolve(1)};// 返回值包含 then 方法
})
p1.then(console.log)
NewPromiseResolveThenableJobTask
Целью NewPromiseResolveThenableJobTask является вызов метода then разрешения и синхронизация состояния с обещанием в функции обратного вызова. Это может быть не очень понятно, я преобразовал его в js примерно так.
microtask(() => {
resolution.then((value) => {
ReslovePromise(promise, value)
})
})
Эта задача вызовет разрешение. Затем, а затем синхронизируется с обещанием. Но весь этот процесс нужно добавить в очередь микрозадачи и дождаться его выполнения.Когда задача запустится, если разрешение тоже Промис, то(value) => {ReslovePromise(promise, value) }
Он будет добавлен в очередь микрозадач как микрозадача и будет ожидать выполнения.
Вы можете задаться вопросом, почему вы это делаете? Почему бы не звонить синхронноresolution.then((value) => {ReslovePromise(promise, value) })
, но инкапсулировать его как микрозадачу? Я сначала тоже был озадачен, но спецификации дают повод.
Примечание. Это задание использует предоставленный thenable и его метод then для разрешения данного промиса. Этот процесс должен выполняться как присваивание, чтобы гарантировать, что метод then оценивается после того, как будет оценен любой окружающий код.
Привести кСпецификация ECMAScript NewPromiseResolveThenableJobTask(авторский перевод)
Что тогда можно:
Концепция, которую генерирует Javascript для распознавания Promise, короче говоря, все объекты, которые содержат метод then, являются затем доступными.
Что означает «гарантировать, что метод then оценивается после оценки любого окружающего кода»? Единственное, о чем я могу думать, это следующая ситуация.
const p1 = new Promise((resolve, reject) => {
const p2 = Promise.resolve().then(() => {
resolve({
then: (resolve, reject) => resolve(1)
});
const p3 = Promise.resolve().then(() => console.log(2));
});
}).then(v => console.log(v));
// 2 1
Обратный вызов onFulfilled для p2, описанный выше, сначала войдет в очередь микрозадач и вызовет разрешение p1 при его выполнении, но параметр является объектом, содержащим метод then. В это время p1 не будет изменен на немедленное выполнение, а создаст микрозадачу для выполнения метода then, а затем добавит p2 onFulfilled в очередь микрозадач. На данный момент в очереди микрозадач есть две микрозадачи: одна — выполнить функцию then в возвращаемом значении разрешения, а другая — функция onFulfilled p3.
Затем возьмите первую микрозадачу на выполнение (после извлечения в очереди микрозадач остается только p3 onFulfilled), состояние p1 становится выполненным после выполнения, а затем в очередь ставится p1 onFulfilled. Можно вывести 2 и 1 последовательно (поскольку функция onFulfilled p1 входит в очередь микрозадач после функции onFulfilled p3).
Если NewPromiseResolveThenableJobTask не используется в качестве микрозадачи. Также становится так, что метод then в параметре разрешения запускается синхронно, когда выполняется обратный вызов в p2.then, и состояние выполнено будет немедленно синхронизировано с p1.В это время onFulfilled p1 сначала войдет в микрозадачу, в результате чего в результате становится 12. Такие результаты выполнения могут сбить с толку разработчиков JavaScript.
Поэтому ECMAScript выполняет его как асинхронную задачу.
Кажется еще более запутанным, что возврат объекта Promsie порождает две микрозадачи.
RejectPromiseReactionJob
В PromiseReactionJob, если при выполнении обработчика возникает исключение, будет выполнен RejectPromiseReactionJob, что является следующей ситуацией
let p0 = Promise.resolve()
let p1 = p0.then(() => {
throw 'error'; // handler 执行时出错
})
Это вызовет RejectPromiseReactionJob, исходный код выглядит следующим образом
macro RejectPromiseReactionJob(
context: Context,
promiseOrCapability: JSPromise|PromiseCapability|Undefined, reason: JSAny,
reactionType: constexpr PromiseReactionType): JSAny {
if constexpr (reactionType == kPromiseReactionReject) {
typeswitch (promiseOrCapability) {
case (promise: JSPromise): {
// promiseOrCapability 就是 p1,是一个 Promise 对象
// 执行 RejectPromise,调用 p1 的 reject 方法
return RejectPromise(promise, reason, False);
}
case (Undefined): {
return Undefined;
}
case (capability: PromiseCapability): {
const reject = UnsafeCast<Callable>(capability.reject);
return Call(context, reject, Undefined, reason);
}
}
} else {
StaticAssert(reactionType == kPromiseReactionFulfill);
return PromiseRejectReactionJob(reason, Undefined, promiseOrCapability);
}
}
RejectPromiseReactionJob похож на FuflfillPromiseReactionJob, то есть вызывает RejectPromise для вызова метода reject в Promsie, который был представлен выше reject.
Обработчик PromiseReactionJob == Неопределенная ветвь
Так же в PromiseReactionJob есть обработчик == Undefined ветка что тоже очень важно.Когда обработчик в задаче undefined он попадет в эту ветку.Для удобства чтения вот код
transitioning
macro PromiseReactionJob(
context: Context, argument: JSAny, handler: Callable|Undefined,
promiseOrCapability: JSPromise|PromiseCapability|Undefined,
reactionType: constexpr PromiseReactionType): JSAny {
if (handler == Undefined) {
// 没有处理函数的 case,透传上一个 Promise 的 argument
if constexpr (reactionType == kPromiseReactionFulfill) {
// 基本类同 JS 层的 resolve
return FuflfillPromiseReactionJob(
context, promiseOrCapability, argument, reactionType);
} else {
// 基本类同 JS 层的 reject
return RejectPromiseReactionJob(
context, promiseOrCapability, argument, reactionType);
}
} else {
// 删除
}
}
После входа в ветку значение и состояние предыдущего объекта Promise будет напрямую получено и синхронизировано с текущим промисом, узнаем об этом через кусок js
let p0 = new Promise((resolve, reject) => {
reject(123)
})
// p0 的状态为 rejected
let p1 = p0.then(_ => {console.log('p0 onFulfilled')})
// p0 的 onRejected 作为 handler 进入 microtask 队列
// 但是因为 then 没有传递第二个参数
// 所以 onRejected 是 undefined,那么 handler 也是 undefined
let 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
После выполнения кода синхронизации (примерно процесс выполнения соответствует комментарию) начинается выполнение микрозадачи, в это время в очереди микрозадач находится только одна задача, обработчик которой не определен. Введите обработчик == Неопределенная ветвь PromiseReactionJob.
Поскольку состояние p0 в это время отклонено, поэтому выполнитеRejectPromiseReactionJob(context, promiseOrCapability, argument, reactionType)
, где promiseOrCapability — это p1, аргумент — это значение p0 123, а responseType отклоняется.
После выполнения состояние p0 также изменяется на responseType, который отклоняется, а значение p1 является аргументом (эквивалентно состоянию и значению p0, передаваемому в p1).
Затем выполните функцию отклонения p1 (FulfillPromise(p1, 123)
), будет ли onRejected (или undefined) в связанном списке PromiseReaction, связанном p1, входить в очередь микрозадач в качестве обработчика (поскольку статус p1 отклонен, поэтому он onRejected)
Также возьмите выполнение задачи микрозадачи, обработчик все еще не определен, а затем то же самое, что и выше, статус отклонен и значение 123 продолжают синхронизироваться с p2, ......
Возьмем снова микрозадачу на выполнение, т.к. p2 привязан к функции onRejected, поэтому обработчик не undefined, то ветвь handler == Undefined не берется, логика другой ветки только что описана. Вероятно, выполните onRejected(123), затем присвойте его результату значение p3, и p3 станет выполненным.
вывод p2 onRejected
Поскольку возвращаемое значение onRejected(123) не определено, p3 становится выполненным со значением undefined
Обратно все то же самое, но обработчик onFulfilled, потому что статус p3 выполнен, что эквивалентноonFulfilled(undefined)
(поскольку значение p3 не определено).
вывод p3 при выполнении
Тогда статус p4 также становится выполненным, и значение также не определено, потому что возвращаемое значение onFulfilled p3 не определено.
Затем p4 onFulfilled становится очередью обработчика, потому что p4 не вызывается, а затем привязывается к обработчику onFulfilled. Но поскольку метод then не вызывается, новый объект Promsie не создается.На этот раз, когда выполняется метод FuflfillPromiseReactionJob, promiseOrCapability имеет значение Undefined и ветвь завершена.
На данный момент все сопутствующие задачи выполнены
Если вы понимаете вышеизложенное, то я думаю, вы сможете узнать результат следующего кода
Promise.resolve('123')
.then(() => {throw new Error('456')})
.then(_ => {
console.log('shouldnot be here')
})
.catch((e) => console.log(e))
.then((data) => console.log(data));
Суть catch(onRejected) тогда (undefined, onRejected)
Это отвергнутый механизм доставки Promise, который продолжает передаваться вниз, пока не столкнется с обработчиком onRejected.
Несколько сложных проблем Promise
Тема 1
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve(4);
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
// 0 1 2 3 4 5 6
В этой статье в основном исследуйте, как обрабатывать, когда значение Promise является многообещающим объектом.Сцепленные вызовы then и очереди микрозадач> ResolvePromiseВведение начинается в конце оглавления
Ключевые слова:thenable,NewPromiseResolveThenableJobTask
отвечать
Для удобства описания преобразуем приведенный выше код в следующий
let p1 = Promise.resolve()
let p2 = p1.then(() => {
console.log(0);
let p3 = Promise.resolve(4)
return p3;
})
let p4 = p2.then((res) => {
console.log(res)
})
let p5 = Promise.resolve()
let p6 = p5.then(() => {
console.log(1);
})
let p7 = p6.then(() => {
console.log(2);
})
let p8 = p7.then(() => {
console.log(3);
})
let p9 = p8.then(() => {
console.log(5);
})
let p10 = p9.then(() => {
console.log(6);
})
Сначала выполните весь синхронный код, процесс выполнения выглядит следующим образом:
let p1 = Promise.resolve()
// 1. p1 的状态为 fulfilled
let p2 = p1.then(() => {
console.log(0);
let p3 = Promise.resolve(4)
return p3;
})
// 2. 因为 p1 的状态已经是 fulfilled,所以调用 then 后立即将 onFulfilled 放入 microtask 队列
// 此时 microtask 只有p1的 onFulfilled: [p1.onFulfilled]
let p4 = p2.then((res) => {
console.log(res)
})
// 3. p2的状态还是 pending,所以调用 then 后是为 p2 收集依赖,此时 p2 的 reactions 如下
/*{
onFulfilled: (res) => {console.log(res)},
onRejected: undefined
}*/
let p5 = Promise.resolve()
// 4. p5 的状态为 fulfilled
let p6 = p5.then(() => {
console.log(1);
})
// 5. 同第2步,将 onFulfilled 加入 microtask 队列
// 此时 microtask 是: [p1.onFulfilled, p5.onFulfilled]
let p7 = p6.then(() => {
console.log(2);
})
// 6. 同第3步,是给 p6 添加 reactions
let p8 = p7.then(() => {
console.log(3);
})
// 7. 同上,是给 p7 添加 reactions
let p9 = p8.then(() => {
console.log(5);
})
// 8. 同上,是给 p8 添加 reactions
let p10 = p9.then(() => {
console.log(6);
})
// 9. 同上,是给 p9 添加 reactions
- При выполнении синхронного кода очередь микрозадач имеет только
[p1.onFulfilled, p5.onFulfilled]
- Затем выньте p1.onFulfilled для выполнения и выведите в это время
0
, но обнаружил, что p3 возвращаемого значения p1.onFulfilled является объектом Promise. Таким образом, будет выполнен блок кода Enqueue ResolvePromise, который вызовет NewPromiseResolveThenableJobTask для создания микрозадачи Действия, выполняемые в этой микрозадаче, были описаны выше, что примерно выглядит следующим образом.
let promiseResolveThenableJobTask = () => {
p3.then((value) => {
ReslovePromise(p2, value)
})
}
Затем добавьте его в очередь микрозадач, и очередь микрозадач станет такой:
[p5.onFulfilled, promiseResolveThenableJobTask]
- Удалить p5.onFulfilled продолжить выполнение, когда вывод
1
, так как возвращаемое значение p5.onFulfilled не определено, поэтому используйте undefined в качестве значения p6, а затем измените состояние p6 на выполнено.
Поскольку состояние p6 изменено, его реакции также будут добавлены в очередь микрозадач, и очередь микрозадач станет такой:
[promiseResolveThenableJobTask,p6.onFulfilled]
- То же самое нужно взять на выполнение promiseResolveThenableJobTask, потому что содержимое promiseResolveThenableJobTask выглядит следующим образом
let promiseResolveThenableJobTask = () => {
p3.then((value) => {
ReslovePromise(p2, value) // ReslovePromise 的作用上面有介绍
})
}
Таким образом, выполнение promiseResolveThenableJobTask эквивалентно выполнениюp3.then((value) => {ReslovePromise(p2, value)})
Поскольку статус p3 выполнен, он будет добавлен в очередь микрозадач onFulfilled (параметр — значение p2 undefined), и очередь микрозадач станет такой:
[p6.onFulfilled,p3.onFulfilled]
- То же самое взять p6.onFulfilled на выполнение, а потом вывести
2
И установите его возвращаемое значение undefined на значение p7 и измените p7 на выполненное состояние, поэтому реакции p7 также будут добавлены в очередь микрозадач, тогда очередь микрозадач станет такой:
[p3.onFulfilled,p7.onFulfilled]
- p3.onFulfilled удаляет выполнение из очереди, p3.onFulfilled
(value) => {ReslovePromise(p2, value)}
, значение параметра не определено, поэтому он выполняется в это времяReslovePromise(p2, undefined)
, что эквивалентно вызову разрешения p2.
Итак, в это время значение p2 становится неопределенным, состояние становится выполненным, а затем его реакции добавляются в очередь микрозадач одну за другой, и в это время очередь микрозадач становится такой:
[p7.onFulfilled,p2.onFulfilled]
- p7.onFulfilled из очереди на выполнение, вывод
3
, статус p8 становится выполненным, значение становится неопределенным, затем p8.onFulfilled присоединяется к очереди
[p2.onFulfilled,p8.onFulfilled]
- p2.onFulfilled из очереди на выполнение, вывод
4
, потому что p2 здесь не вызывается метод then, поэтому следующий объект Promise не генерируется, поэтому пост-порядок отсутствует.
[p8.onFulfilled]
- Излишне говорить позже
Тема 2
Promise.resolve().then(() => {
console.log(0);
return {then(resolve){resolve(4)}};
}).then((res) => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
// 0 1 2 4 3 5 6
Соответствующие с названием знания, исследование - это логическое значение, которое происходит, когда объект, который является тем, что способ для обещания, включающий
Ключевые слова:thenable,NewPromiseResolveThenableJobTask
тема 3
const p1 = new Promise((resolve, reject) => {
reject(0)
})
console.log(1);
setTimeout(() => {
p1.then(undefined, console.log)
}, 0)
console.log(2);
// 1
// 2
// 输出报错 UnhandledPromiseRejection: This error originated either
const p1 = new Promise((resolve, reject) => {
reject(0)
})
console.log(1);
p1.then(undefined, console.log)
console.log(2);
// 1
// 2
// 0
Почему первый метод сообщает об ошибке?
Исследуется HostPromiseRejectionTracker в спецификации. Когда Promsie без связанной функции обработчика вызывается reject, будет создана микрозадача, чтобы проверить, есть ли у обещания функция обработчика снова. Если она не существует в это время, будет ошибка. будет выводиться, а обратный вызов setTimeout будет выполняться после выполнения микрозадач.
эта статьяreject > HostPromiseRejectionTrackerПодробности в каталоге.
Уведомление:Консоль браузера имеет очень странную особенность, если привязать к ней обработчик onrejected после вывода этой ошибки, то браузер перезапишет ошибку консоли. Поэтому, если вы выполняете этот код в браузере, установите время setTimeout на большее время, чтобы эффект был легче виден невооруженным глазом.
тема четвертая
Почему вывод async1 заканчивается после обещания3?
async function async1() {
console.log("async1 start");
await async2();
console.log("async1 end");
}
async function async2() {
console.log("async2");
return Promise.resolve().then(() => {
console.log("async2-inner");
});
}
console.log("script start");
setTimeout(function () {
console.log("settimeout");
});
async1();
new Promise(function (resolve) {
console.log("promise1");
resolve();
})
.then(function () {
console.log("promise2");
})
.then(function () {
console.log("promise3");
})
.then(function () {
console.log("promise4");
});
console.log("script end");
Спасибо за вклад в тему в разделе комментариев
отвечать
Эта проблема, связанная с использованием await в асинхронном режиме, будет генерировать несколько ссылок Promise, а проблема в том, что значение разрешения является объектом Promise, сосредоточив внимание на async2, я преобразовал его в это (setTimeout не слишком напряжен для этого вопроса, поэтому я удалил это).
async function async1() {
console.log("async1 start");
let a2 = async2();
await a2
console.log("async1 end");
}
async function async2() {
console.log("async2");
let p1 = Promise.resolve()
let p2 = p1.then(() => {
console.log("async2-inner");
})
return p2;
}
let a1 = async1();
let p3 = new Promise(function (resolve) {
console.log("promise1");
resolve();
})
let p4 = p3.then(function () {
console.log("promise2");
})
// 为 p3 添加 reactions
let p5 = p4.then(function () {
console.log("promise3");
})
// 为 p4 添加 reactions
let p6 = p5.then(function () {
console.log("promise4");
});
// 为 p5 添加 reactions
console.log("script end");
Вы, должно быть, также обнаружили, что код перед оператором ожидания в асинхронной функции выполняется синхронно (эквивалентно исполнителю Promsie).
- Сначала вызовите async1, вывод
async1 start
, выполняется синхронно сasync2()
позицию, а затем перейти к синхронному выполнению async2.
затем выводasync2
, который создает состояние выполнения p1, а затем привязывает p1 к then, так как p1 находится в состоянии выполнения, p1.onFulfilled немедленно попадет в очередь микрозадач. В этот момент очередь микрозадач становится такой:
[p1.onFulfilled]
Затем верните p2 (resolve(p2)), и, поскольку p2 является объектом Promise, создайте promiseResolveThenableJobTask следующим образом.
let promiseResolveThenableJobTask = () => {
p2.then((value) => {
ReslovePromise(a2, value) // ReslovePromise 的作用上面有介绍
})
}
[p1.onFulfilled,promiseResolveThenableJobTask]
- Затем выполните команду await a2, что здесь очень важно.
a2.then(_ => {
// 这里是 async1 中 await 后的代码
})
// 为 a2 绑定了 reactions,这里的 onFulfill 暂时就叫 『async1后半段』吧
// 其实这里面做的事情很多,我后面可能会单独讲解 async/await 的原理
Обратите внимание, что a2 в настоящее время не выполняется, потому что ему нужно дождаться выполнения promiseResolveThenableJobTask, чтобы вызвать его разрешение, прежде чем оно станет выполненным.
- В это время выполняется код синхронизации, запущенный async1(), и новое обещание продолжает выполняться.
Выполнить этот код синхронно
function (resolve) {
console.log("promise1");
resolve();
}
выходpromise1
, выполнить resolve, после чего статус p3 становится выполненным, p3.onFulfilled ставится в очередь, а последующие потом все реакции бинда на соответствующий промсье, не говоря уже об этом, и, наконец, выводscript end
На этом все синхронное выполнение кода завершено, и очередь микрозадач выглядит так:
[p1.onFulfilled,promiseResolveThenableJobTask,p3.onFulfilled]
- На этом все синхронное выполнение кода завершено, начинается выполнение микрозадачи, сначала p1.onFulfilled , результат выполнения
async2-inner
, затем использует возвращаемое значение undefined в качестве значения p2 и переводит p2 в состояние выполнения. Так как у p2 в это время нет реакций (то есть метод then не вызывался), ничего не произойдет
[promiseResolveThenableJobTask,p3.onFulfilled]
- promiseResolveThenableJobTask выполняется из очереди, и его содержимое выглядит следующим образом, как указано выше.
let promiseResolveThenableJobTask = () => {
p2.then((value) => {
ReslovePromise(a2, value) // ReslovePromise 的作用上面有介绍
})
}
Выполнение заключается в выполнении метода then объекта p2 для привязки к нему обработчика onFulfilled, но p2 уже выполнен, поэтому он напрямую добавит p2.onFulfilled в очередь микрозадач.
[p3.onFulfilled, p2.onFulfilled]
- p3.onFulfilled удаление из очереди, вывод
promise2
, изменить статус p4 на выполнено, значение p4 является его 1 возвращаемым значением, которое не определено, а затем onFulfilled p4 также присоединится к очереди микрозадач
[p2.onFulfilled, p4.onFulfilled]
- p2.onFulfilled выполняется из очереди, а содержимое p2.onFulfilled выглядит следующим образом, как указано выше.
(value) => {
ReslovePromise(a2, value) // 也就是 a2 的 resolve(value)
}
Таким образом, после выполнения ReslovePromise, a2 станет выполненным, а a2.onFulfilled, который является второй половиной async1, естественным образом попадет в очередь микрозадач.
[p4.onFulfilled, async1后半段]
- В последующих результатах нет никаких сложностей, p4.onFulfilled выполняется вне очереди, а вывод
promise3
, то p5.onFulfilled ставится в очередь
[async1后半段,p5.onFulfilled]
- Вторая половина async1 удаляется из очереди и выполняется, вывод
async1 end
, то состояние a1 становится выполненным, но ни один обработчик не привязан, поэтому a1 не имеет продолжения
[p5.onFulfilled]
- p5.onFulfilled вывод выполнения удаления из очереди
promise4
, аналогично p6 не связывает никакую функцию обработки, пока выполнение всего кода завершено
последние несколько вещей
-
Если у вас есть какие-либо вопросы по этой статье, пожалуйста, оставьте комментарий
-
Если вы просматриваете эту статью и по-прежнему не можете получить ответ на вышеуказанный вопрос, сообщите нам об этом в области комментариев, и позже я дам подробное введение в процесс реализации.
-
Если у вас есть какие-либо проблемы с Promise, вы можете оставить комментарий, и автор бесплатно уладит все проблемы с Promise.
-
Если вы видите это, но оно еще не понравилось, пожалуйста, поставьте лайк, большое спасибо.
Ссылки по теме
Анализ исходного кода Promise V8 (1) - Сюй Пэнъюэ
Что происходит после возврата Promise.resolve в promise.then?