Шпаргалка по внутреннему устройству промисов (ECMAScript).
Описанные здесь алгоритмы - моя интерпретация спецификации, так как некоторые шаги опущены/видоизменены для простоты.
Конструктор Promise - Promise ( executor )
1. Если Promise вызван без new, выбросить TypeError.
2. Если executor не является функцией, выбросить TypeError.
3. Создать объект promise со свойствами:
[[PromiseState]]: PENDING
[[PromiseResult]]: undefined
[[PromiseFulfillReactions]]: [] (записи, подлежащие обработке если promise переходит из состояния pending в fulfilled)
[[PromiseRejectReactions]]: [] (записи, подлежащие обработке если promise переходит из состояния pending в rejected)
[[PromiseIsHandled]]: false (указывает, имел ли promise когда-либо обработчик; используется для отслеживания необработанных отклонений)
4. Создать переменную resolvingFunctions = CreateResolvingFunctions(promise).
5. Вызвать executor(resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]]).
6. Вернуть promise.
Создание resolving-функций - CreateResolvingFunctions ( promise )
1. Создать переменную alreadyResolved = Record { [[Value]]: false }
2. Создать функцию resolve и сохранить в ней алгоритм Promise Resolve Functions, передав туда promise и alreadyResolved, затем
a. Выполнить resolve.[[Promise]] = promise
b. Выполнить resolve.[[AlreadyResolved]] = alreadyResolved
3. Создать функцию reject и сохранить в ней алгоритм Promise Reject Functions, затем
a. Выполнить reject.[[Promise]] = promise
b. Выполнить reject.[[AlreadyResolved]] = alreadyResolved
4. Вернуть Record { [[Resolve]]: resolve, [[Reject]]: reject }.
Функция resolve - Promise Resolve Functions
Когда resolve вызывается с аргументом, называемым resolution, выполняются следующие шаги:
1. Если resolve.[[AlreadyResolved]].[[Value]] === true, вернуть undefined.
2. Установить resolve.[[AlreadyResolved]].[[Value]] в true.
3. Если resolution === promise,
a. Создать переменную selfResolutionError - экземпляр класса TypeError
b. Выполнить RejectPromise(promise, selfResolutionError)
c. Вернуть undefined
4. Если resolution - не объект,
a. Выполнить FulFillPromise(promise, resolution)
b. Вернуть undefined
5. Создать переменную thenAction = resolution.then.
6. Если resolution - объект с геттером then, выбрасывающим ошибку,
a. Выполнить RejectPromise(promise, thenAction)
b. Вернуть undefined
7. Если thenAction - не функция,
a. Выполнить FulfillPromise(promise, resolution)
b. Вернуть undefined
8. Создать переменную thenJobCallback = HostMakeJobCallback(thenAction).
9. Создать переменную job = NewPromiseResolveThenableJob(promise, resolution, thenJobCallback).
10. Выполнить HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]).
11. Вернуть undefined.
Функция reject - Promise Reject Functions
Когда функция reject вызывается с аргументом, называемым reason, выполняются следующие шаги:
1. Если reject.[[AlreadyResolved]].[[Value]] === true, вернуть undefined.
2. Установить reject.[[AlreadyResolved]].[[Value]] в true.
3. Выполнить RejectPromise(promise, reason).
4. Вернуть undefined.
Алгоритм FulfillPromise - FulfillPromise ( promise, value )
1. Создать переменную reactions = promise.[[PromiseFulfillReactions]].
2. Выполнить promise.[[PromiseResult]] = value.
3. Установить promise.[[PromiseFulfillReactions]] в undefined.
4. Установить promise.[[PromiseRejectReactions]] в undefined.
5. Выполнить promise.[[PromiseState]] = FULFILLED.
6. Выполнить TriggerPromiseReactions(reactions, value).
Алгоритм RejectPromise - RejectPromise ( promise, reason )
1. Создать переменную reactions = promise.[[PromiseRejectReactions]].
2. Выполнить promise.[[PromiseResult]] = reason.
3. Установить promise.[[PromiseFulfillReactions]] в undefined.
4. Установить promise.[[PromiseRejectReactions]] в undefined.
5. Выполнить promise.[[PromiseState]] = REJECTED.
6. Если promise.[[PromiseIsHandled]] === false, выполнить HostPromiseRejectionTracker(promise, "reject").
a. (сообщить host-среде о необработанной ошибке)
7. Выполнить TriggerPromiseReactions(reactions, reason).
Алгоритм TriggerPromiseReactions - TriggerPromiseReactions ( reactions, argument )
Алгоритм ставит в очередь новую задачу для каждой записи в reactions:
1. Для каждой записи в reactions выполнить:
a. Создать переменную job = NewPromiseReactionJob(reaction, argument)
b. Выполнить HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]])
Promise.prototype.then - Promise.prototype.then ( onFulfilled, onRejected )
1. Создать переменную promise и присвоить ей значение this.
2. Если IsPromise(promise) вернет false, выбросить TypeError.
a. IsPromise(x) === true если x - объект с внутренним слотом [[PromiseState]], иначе false.
3. Создать переменную C = promise.constructor[Symbol.species] ?? Promise.
4. Создать переменную resultCapabiity = NewPromiseCapability(C).
5. Вернуть PerformPromiseThen(promise, onFulfilled, onRejected, resultCapability).
Запись возможности promise - NewPromiseCapability ( C )
1. Если IsConstructor(C) вернет false, выбросить TypeError.
a. IsConstructor(argument) === true если argument - объект c внутренним методом [[Construct]], иначе false.
2. Создать переменную resolvingFunctions = Record { [[Resolve]]: undefined, [[Reject]]: undefined }.
3. Создать функцию-замыкание executorClosure(resolve, reject). Ее алгоритм при вызове:
a. Если resolvingFunctions.[[Resolve]] !== undefined, выбросить TypeError
b. Если resolvingFunctions.[[Reject]] !== undefined, выбросить TypeError
c. Выполнить resolvingFunctions.[[Resolve]] = resolve
d. Выполнить resolvingFunctions.[[Reject]] = reject
e. Вернуть undefined
4. Создать переменную promise = new C(executorClosure).
5. Если resolvingFunctions.[[Resolve]] не функция, выбросить TypeError.
6. Если resolvingFunctions.[[Reject]] не функция, выбросить TypeError.
7. Вернуть Record { [[Promise]]: promise, [[Resolve]]: resolvingFunctions.[[Resolve]], [[Reject]]: resolvingFunctions.[[Reject]] }.
Алгоритм PerformPromiseThen - PerformPromiseThen ( promise, onFulfilled, onRejected [ , resultCapability ] )
1. Объявить переменную onFuilfilledJobCallback, затем
a. Если onFulfilled не функция, выполнить onFulfilledJobCallback = EMPTY
b. Иначе выполнить onFulfilledJobCallback = HostMakeJobCallback(onFulfilled)
2. Объявить переменную onRejectedJobCallback, затем
a. Если onRejected не функция, выполнить onRejectedJobCallback = EMPTY
b. Иначе выполнить onRejectedJobCallback = HostMakeJobCallback(onRejected)
3. Объявить переменную fulfillReaction = Record { [[Capability]]: resultCapability, [[Type]]: FULFILL, [[Handler]]: onFulfilledJobCallback }.
4. Объявить переменную rejectReaction = Record { [[Capability]]: resultCapability, [[Type]]: REJECT, [[Handler]]: onRejectedJobCallback }.
5. Если promise.[[PromiseState]] === PENDING,
a. promise.[[PromiseFulfillReactions]].push(fulfillReaction)
b. promise.[[PromiseRejectReactions]].push(rejectReaction)
6. Если promise.[[PromiseState]] === FULFILLED, (то поставить fulfillReaction в очередь)
a. Создать переменную value = promise.[[PromiseResult]]
b. Создать переменную fulfillJob = NewPromiseReactionJob(fulfillReaction, value)
c. Выполнить HostEnqueuePromiseJob(filfillJob.[[Job]], filfillJob.[[Realm]])
7. Иначе promise.[[PromiseState]] === REJECTED, (поставить RejectReaction в очередь)
a. Создать переменную reason = promise.[[PromiseResult]]
b. Создать переменную rejectJob = NewPromiseReactionJob(rejectReaction, reason)
c. Выполнить HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]])
8. Установить promise.[[PromiseIsHandled]] в true.
9. Вернуть resultCapability.[[Promise]].
Promise.prototype.catch - Promise.prototype.catch ( onRejected )
Вызов метода .catch(value) - то же, что и вызов метода .then(undefined, value).
1. Создать переменную promise и присвоить ей значение this.
2. Вернуть promise.then(undefined, onRejected).
Promise.prototype.finally - Promise.prototype.finally ( onFinally )
1. Создать переменную promise и присвоить ей значение this.
2. Если promise не объект, выбросить TypeError.
3. Создать переменную C = promise.constructor[Symbol.species] ?? Promise.
4. Если onFinally не функция, вернуть promise.then(onFinally, onFinally).
5. Создать функцию-замыкание thenFinally(value), имеющую ссылку на onFinally и C, которая при вызове:
a. Создаст переменную result = onFinally();
b. Создаст переменную p = Promise.resolve.call(C, result);
c. Создаст функцию-замыкание valueThunk(), имеющую ссылку на value и при вызове возвращающую value.
d. Возвращает p.then(valueThunk)
6. Создать функцию-замыкание catchFinally(reason), имеющую ссылку на onFinally и C, которая при вызове:
a. Создаст переменную result = onFinally();
b. Создаст переменную p = Promise.resolve.call(C, result);
c. Создаст функцию-замыкание thrower(), имеющую ссылку на reason и при вызове выполняющую throw reason.
d. Возвращает p.then(thrower)
7. Вернуть promise.then(thenFinally, catchFinally).
Realm - глобальная среда, в которой выполняется код на JavaScript. Она имеет встроенные свойства, например, глобальный объект. Как правило, на один realm приходится один объект window.
Settings object - объект настроек окружения для realm.
Browsing context - контекст просмотра - окружение, в котором браузер отображает объекты document пользователю, например, tab, window, iframe - содержат контекст просмотра.
1. HostMakeJobCallback - создание специальной записи, которая содержит непосредственно функцию и набор настроек для ее вызова.
2. NewPromiseReactionJob/NewPromiseResolveThenableJob - создание записи, которая содержит задачу и ее Realm.
3. HostEnqueuePromiseJob - постановка задачи в очередь для ее исполнения планировщиком задач.
4. HostCallJobCallback - вызов функции обратного вызова, которая находится внутри кода задачи.
HostMakeJobCallback - HostMakeJobCallback( callback )
1. Получить действующие настройки кода.
2. Создать переменную execution context.
3. Если есть активный скрипт, скопировать его ключевые поля в execution context.
4. Создать запись задачи Record { [[Callback]]: callback, [[HostDefined]]: { [[IncumbentSettings]]: settings, [[ActiveScriptContext]]: execution context } }.
NewPromiseReactionJob - NewPromiseReactionJob ( reaction, argument )
1. Создать функцию-замыкание job(), имеющую ссылку на reaction и argument, которая при вызове:
a. Создать переменную promiseCapability = reaction.[[Capability]]
b. Создать переменную type = reaction.[[Type]]
c. Создать переменную handler = reaction.[[Handler]]
d. Создать переменную handlerResult
e. Если handler === EMPTY,
i. Если type === FULFILL, выполнить handlerResult = Record { [[Type]]: NORMAL, [[Value]]: argument, [[Target]]: EMPTY }.
ii. Иначе выполнить handlerResult = ThrowCompletion(argument)
f. Иначе выполнить handlerResult = Completion(HostCallJobCallback(handler, undefined, « argument »))
g. Если promiseCapability === undefined, вернуть EMPTY
h. Если handlerResult - abrupt complition, вернуть promiseCapability.[[Reject]](handlerResult.[[Value]])
i. Иначе вернуть promiseCapability.[[Resolve]](handlerResult.[[Value]])
2. Создать переменную handlerRealm = null.
3. Если reaction.[[Handler]] !== EMPTY, [... создать handlerRealm].
4. Вернуть Record { [[Job]]: job, [[Realm]]: handlerRealm }.
NewPromiseResolveThenableJob - NewPromiseResolveThenableJob ( promiseToResolve, thenable, then )
1. Создать функцию-замыкание job(), имеющую ссылку на promiseToResolve, thenable и then, алгоритм при ее вызове:
a. Создать переменную resolvingFunctions = CreateResolvingFunctions(promiseToResolve)
b. Создать переменную thenCallResult = Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »))
c. Если вызов thenCallResult - abrupt completion, вернуть resolvingFunctions.[[Reject]](thenCallResult.[[Value]])
d. Вернуть thenCallResult
2. Создать переменную getThenRealmResult = Completion(GetFunctionRealm(then.[[Callback]])).
3. Объявить переменную thenRealm.
4. Если getThenRealmResult - normal completion, выполнить thenRealm = getThenRealmResult.[[Value]].
a. Иначе выполнить thenRealm = current Realm Record.
5. Вернуть Record { [[Job]]: job, [[Realm]]: thenRealm }.
HostEnqueuePromiseJob - HostEnqueuePromiseJob ( job, realm )
Функция планирует выполнение job в будущем (ставит в очередь микрозадач).
HostCallJobCallback - HostCallJobCallback ( jobCallback, V, argumentsList )
Функция возвращает jobCallback.[[Callback]].apply(V, argumentList)