Skip to content

Instantly share code, notes, and snippets.

@sdrjs
Last active July 11, 2024 04:39
Show Gist options
  • Save sdrjs/cb6170a5ab5e34af17994b5d152d9553 to your computer and use it in GitHub Desktop.
Save sdrjs/cb6170a5ab5e34af17994b5d152d9553 to your computer and use it in GitHub Desktop.
Promise с точки зрения ECMAScript

Шпаргалка по внутреннему устройству промисов (ECMAScript).

Описанные здесь алгоритмы - моя интерпретация спецификации, так как некоторые шаги опущены/видоизменены для простоты.

Оболочка Promise

Конструктор 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

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]] }.
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 } }.
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 }.
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 в будущем (ставит в очередь микрозадач).
Функция возвращает jobCallback.[[Callback]].apply(V, argumentList)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment