предисловие
ранееPromise
Синтаксис Боуэн написал, но только начальный уровень, мало еды и безвкусно. Всегда хотел написать ответPromise
Осознал, что из-за ограниченного понимания я много раз не был удовлетворен написанным. Откладывалось и откладывается, по сей день.
вместе сСпецификация Обещание/A+,Спецификация ECMAscriptправильноPromise
API сформулирован и выполнен, и базовая единица асинхронной операции Javascript постепенно изменилась сcallback
Преобразование вpromise
. подавляющее большинствоJavaScript/DOM
Новый асинхронный API платформы (Fetch
,Service worker
) также основаны наPromise
построен. этой парыPromise
Понимание — это не то, что можно полностью понять, просто взглянув на API и прочитав несколько практик. Таким образом, автор анализирует детали, растет вместе с читателями и движется вперед.
Эта статья является второй частью серии практических решений для внешнего интерфейса асинхронного программирования, основной анализPromise
Внутренний механизм и принцип реализации. Последующие асинхронные серии также будут включатьGenerator
,Async/Await
Связанные, выкопайте яму, чтобы занять место.
Примечание: эта статьяPromise
подчинятьсяPromises/A+спецификация, ссылка на реализациюthen/promise.
Promise
A promise represents the eventual result of an asynchronous operation. --Promises/A+
A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation. --ECMAscript
Promises/A+
ECMAscript
Promise
callback
Обработка асинхронных обратных вызовов может привести к преждевременным вызовам, слишком поздним вызовам, слишком большому или слишком малому количеству вызовов, поглощению возможных ошибок или исключений и т. д. Кроме того, обещают принять только в первый разresolve(..)
илиreject(..)
Разрешение, обещает, что не изменится после изменения состояния, обещает, что все прошлоthen(..)
Зарегистрированные обратные вызовы всегда вызываются асинхронно по очереди, обещая, что все исключения всегда будут перехвачены и выброшены. Она надежное обещание.
Строго говоря,Promise
Это простой в использовании механизм для инкапсуляции и объединения будущих значений, реализации разделения задач, асинхронного управления потоком, всплытия исключений, последовательного/параллельного управления и т. д.
Примечание: упоминается в текстеcallback
Подробнее о проблеме см. в главах > 2.3, 3.3.
Стандартная интерпретация
Promise A+
Спецификация короткая и лаконичная, но читайте внимательно, есть еще несколько моментов, требующих внимания.
пригодный для использования объект
thenable
это определениеthen(..)
метод объекта или функции.thenable
Объект существует, чтобы сделатьPromise
реализация является более общей, поскольку она предоставляетPromise/A+
нормативныйthen(..)
метод. также будет следоватьPromise/A+
Канонические реализации могут хорошо сосуществовать с менее каноническими, но пригодными для использования реализациями.
Идентифицироватьthenable
или вести себя какPromise
Объект может быть основан на том, имеет ли онthen(..)
способ судить, который на самом деле называетсяпроверка типовтакже называемыйутиные дебаты(duck typing
). дляthenable
Обнаружение типа утки значения примерно похоже на:
if ( p !== null &&
(
typeof p === 'object' ||
typeof p === 'function'
) &&
typeof p.then === 'function'
) {
// thenable
} else {
// 非 thenable
}
затем обратный вызов выполняется асинхронно
Как мы все знаем,Promise
Функция, переданная при создании экземпляра, будет выполнена немедленно,then(...)
Обратные вызовы необходимо вызывать асинхронно и откладывать. Что касается того, почему звонок задерживается, я объясню позже. Здесь есть важный момент, время асинхронного вызова функции обратного вызова.
onFulfilled or onRejected must not be called until the execution context stack contains only platform code --Promise/A+
сокращенноonFulfilled
илиonRejected
Непосредственно перед вызовом среды выполнения, когда стек содержит только код платформы. Слегка озадаченный,Спецификация Обещание/A+Это предложение также поясняется: «На практике необходимо обеспечить, чтобыonFulfilled
а такжеonRejected
метод выполняется асинхронно и должен бытьthen
Выполняется в новом стеке выполнения после цикла обработки событий, в котором был вызван метод. Эта очередь событий может использоватьмакрозадачамеханизм илимикрозадачамеханизм достижения. "
несмотря на то чтоPromise A+
прямо не указаноmicrotask
ещеmacrotask
встать в очередь, ноECMAScript
В спецификации четко указаноPromise
должен начинаться сPromise JobДобавлено какjob queues
(он же микрозадача).Job QueueЭто новая концепция, предложенная в ES6, построенная на очереди цикла событий.job queue
Он также существует для выполнения некоторых асинхронных операций с малой задержкой.
Постучите по доске, чтобы сосредоточиться, обратите внимание на этоmacrotask
microtask
Представляют две категории асинхронных задач соответственно. При приостановке задач движок JS разделит все задачи на две очереди по категориям.macrotask
очередь (также называемаяtask queue
), возьмите первую задачу в очереди микрозадач и выполните ее последовательно; затем возьмите задачу макрозадачи и повторяйте, пока не будут удалены все задачи в двух очередях.
дляmicrotask
время исполнения,спецификация HTML whatwgЭто также объясняется в деталях, пожалуйста, нажмите, чтобы просмотреть. Дополнительные статьи по теме см. в приложенииevent loop
.
Посмотрите на другой пример, чтобы углубить ваше понимание:
setTimeout(function () {
console.log('setTimeout');
}, 0);
Promise.resolve().then(function () {
console.log('promise1');
}).then(function () {
console.log('promise2');
});
заказ печати? Правильный ответ:promise1, promise2, setTimeout
.
в дальнейшей реализацииPromise
Перед объектом просто смоделируйте функцию асинхронного выполнения для последующего использования.Promise
Использование обратного вызова (можно также использоватькак можно скорее библиотекаЖдать).
var asyncFn = function () {
if (typeof process === 'object' && process !== null &&
typeof(process.nextTick) === 'function'
) {
return process.nextTick;
} else if (typeof(setImmediate) === 'function') {
return setImmediate;
}
return setTimeout;
}();
Состояние обещания
Promise
Должно быть одно из следующих трех состояний: состояние ожидания (Pending
), состояние выполнения (Fulfilled
) и состояние отказа (Rejected
). однаждыPromise
одеялоresolve
илиreject
, больше не может быть перенесен в любое другое состояние (т.е. состояниеimmutable
).
Чтобы код оставался ясным, в настоящее время нет обработки исключений. В то же время для удобства выражения соглашение выглядит следующим образом:
- выполненное использование разрешено вместо этого
- вместо onFulfilled используйте onResolved
Конструктор обещаний
Начиная с конструктора, мы шаг за шагом добиваемсяPromsie A+
нормативныйPromise
.大概描述下,Promise
Что должен сделать конструктор.
- инициализация
Promise
условие(pending
) - инициализация
then(..)
Регистрация обратных вызовов для обработки массивов (then
метод может быть использован тем жеpromise
звонил несколько раз) - Немедленно выполнять входящие
fn
функция, пройти вPromise
внутреннийresolve
,reject
функция - ...
function Promise (fn) {
// 省略非 new 实例化方式处理
// 省略 fn 非函数异常处理
// promise 状态变量
// 0 - pending
// 1 - resolved
// 2 - rejected
this._state = 0;
// promise 执行结果
this._value = null;
// then(..) 注册回调处理数组
this._deferreds = [];
// 立即执行 fn 函数
try {
fn(value => {
resolve(this, value);
},reason => {
reject(this, reason);
})
} catch (err) {
// 处理执行 fn 异常
reject(this, err);
}
}
_state
а также_value
Переменные легко понять,_deferreds
Что делают переменные? Описание спецификации:then
методы могут быть использованы одним и тем жеpromise
Звонили несколько раз. Чтобы удовлетворить несколько вызововthen
Зарегистрировать обработку обратного вызова, внутренне выбранную для использования_deferreds
Массивы хранят объекты дескрипторов. Для конкретной структуры объекта обработки см.then
главу функции.
окончательное исполнениеfn
Функции и вызовыpromise
внутренний частный методresolve
а такжеreject
.resolve
а такжеreject
Внутренние детали вводятся позже.
затем функция
Promise A+
Упомяните, что спецификация ориентирована на предоставление универсальногоthen
метод.then
методы могут быть использованы одним и тем жеpromise
Вызывается несколько раз, каждый раз возвращая новыйpromise
объект .then
Метод принимает два параметраonResolved
,onRejected
(необязательный). существуетpromise
одеялоresolve
илиreject
после всегоonResolved
илиonRejected
Функции должны вызываться в том порядке, в котором они были зарегистрированы, а количество вызовов не должно превышать одного.
Согласно вышеизложенному,then
Схема выполнения функции примерно следующая:
- создать пустой экземпляр
promise
Возвращается к объекту (проведениеthen
цепной звонок) - структура
then(..)
зарегистрировать структуру обработчика обратного вызова - судить о текущем
promise
условие,pending
Объекты ленивой обработки хранилища состоянийdeferred
,Нетpending
государственное исполнениеonResolved
илиonRejected
Перезвоните - ...
Promise.prototype.then = function (onResolved, onRejected) {
var res = new Promise(function () {});
// 使用 onResolved,onRejected 实例化处理对象 Handler
var deferred = new Handler(onResolved, onRejected, res);
// 当前状态为 pendding,存储延迟处理对象
if (this._state === 0) {
this._deferreds.push(deferred);
return res;
}
// 当前 promise 状态不为 pending
// 调用 handleResolved 执行onResolved或onRejected回调
handleResolved(this, deferred);
// 返回新 promise 对象,维持链式调用
return res;
};
Handler
хранение инкапсуляции функцийonResolved
,onRejected
функции и новое поколениеpromise
объект.
function Handler (onResolved, onRejected, promise) {
this.onResolved = typeof onResolved === 'function' ? onResolved : null;
this.onRejected = typeof onRejected === 'function' ? onRejected : null;
this.promise = promise;
}
Почему цепочка вызовов возвращает новое обещание
Как мы понимаем, для гарантииthen
цепочка функций,then
нужно вернутьсяpromise
пример. а зачем возвращать новыйpromise
, вместо прямого возвратаthis
Текущий объект? См. следующий пример кода:
var promise2 = promise1.then(function (value) {
return Promise.reject(3)
})
еслиthen
выполнение функции возвращаетthis
вызвать сам объект, затемpromise2 === promise1
,promise2
Статус также должен быть равенpromise1
Такой же какresolved
. а такжеonResolved
Статус возврата в обратном вызовеrejected
объект. учитываяPromise
заявить один разresolved
илиrejected
больше не может быть перенесен, так что здесьpromise2
Невозможно преобразовать его в функцию возврата функции обратного вызова.rejected
Состояние возникает конфликт.
handleResolved
Функциональная функция основана на текущейpromise
состояние, выполняемое асинхронноonResolved
илиonRejected
Перезвоните. потому чтоresolve
илиreject
Связанные функции также требуются внутри функции, которая извлекается как отдельный модуль. Прокрутите вниз, чтобы просмотреть.
разрешающая функция
Promise
Выполняет переданное немедленно при создании экземпляраfn
функцию, при этом проходя внутреннююresolve
функционировать как параметр для измененияpromise
условие.resolve
Логика упрощенной версии функции, наверное, такова: судить и менять текущуюpromise
состояние, хранениеresolve(..)
изvalue
стоимость. Определить, существует ли он в настоящее времяthen(..)
Зарегистрируйте функцию выполнения обратного вызова, если она существует, она будет выполняться асинхронно последовательноonResolved
Перезвоните.
Но как начало текстаthenable
описание главы, сделатьPromise
Реализация является более общей, когдаvalue
существоватьthen(..)
методthenable
объект, который необходимо сделатьPromise Resolution Procedure
обработка, спецификация описывает это как[[Resolve]](promise, x)
. (x
то есть позадиvalue
параметр).
Конкретный логический поток обработки выглядит следующим образом:
-
если
promise
а такжеx
указать на тот же объект, что иTypeError
отказ от исполненияpromise
-
если
x
дляPromise
, затем сделайтеpromise
приниматьx
положение дел -
если
x
как объект или функция- Пучок
x.then
назначить наthen
- Если взять
x.then
выдает ошибку e , отклоняет на основании epromise
- если
then
это функция, которая будетx
Назовите это как область действия функции this . - если
x
не является объектом или функцией, сx
выполнить для параметровpromise
- Пучок
исходная ссылкаPromise A+
Технические характеристикиPromise Resolution Procedure.
function resolve (promise, value) {
// 非 pending 状态不可变
if (promise._state !== 0) return;
// promise 和 value 指向同一对象
// 对应 Promise A+ 规范 2.3.1
if (value === promise) {
return reject( promise, new TypeError('A promise cannot be resolved with itself.') );
}
// 如果 value 为 Promise,则使 promise 接受 value 的状态
// 对应 Promise A+ 规范 2.3.2
if (value && value instanceof Promise && value.then === promise.then) {
var deferreds = promise._deferreds
if (value._state === 0) {
// value 为 pending 状态
// 将 promise._deferreds 传递 value._deferreds
// 偷个懒,使用 ES6 展开运算符
// 对应 Promise A+ 规范 2.3.2.1
value._deferreds.push(...deferreds)
} else if (deferreds.length !== 0) {
// value 为 非pending 状态
// 使用 value 作为当前 promise,执行 then 注册回调处理
// 对应 Promise A+ 规范 2.3.2.2、2.3.2.3
for (var i = 0; i < deferreds.length; i++) {
handleResolved(value, deferreds[i]);
}
// 清空 then 注册回调处理数组
value._deferreds = [];
}
return;
}
// value 是对象或函数
// 对应 Promise A+ 规范 2.3.3
if (value && (typeof value === 'object' || typeof value === 'function')) {
try {
// 对应 Promise A+ 规范 2.3.3.1
var then = obj.then;
} catch (err) {
// 对应 Promise A+ 规范 2.3.3.2
return reject(promise, err);
}
// 如果 then 是函数,将 value 作为函数的作用域 this 调用之
// 对应 Promise A+ 规范 2.3.3.3
if (typeof then === 'function') {
try {
// 执行 then 函数
then.call(value, function (value) {
resolve(promise, value);
}, function (reason) {
reject(promise, reason);
})
} catch (err) {
reject(promise, err);
}
return;
}
}
// 改变 promise 内部状态为 `resolved`
// 对应 Promise A+ 规范 2.3.3.4、2.3.4
promise._state = 1;
promise._value = value;
// promise 存在 then 注册回调函数
if (promise._deferreds.length !== 0) {
for (var i = 0; i < promise._deferreds.length; i++) {
handleResolved(promise, promise._deferreds[i]);
}
// 清空 then 注册回调处理数组
promise._deferreds = [];
}
}
resolve
Логика функции более сложная и в основном сосредоточена на обработкеvalue
(x
) оценивают несколько возможностей. еслиvalue
дляPromise
и статусpending
когдаpromise
приниматьvalue
положение дел. существуетvalue
Состояниеpending
, Проще говоряpromise
изdeferreds
Дан массив обработки обратного вызоваvalue
deferreds
Переменная. Нетpending
состояние, использованиеvalue
Обратный вызов внутреннего значенияpromise
Зарегистрированоdeferreds
.
еслиvalue
дляthenable
объект, сvalue
Объем как функцияthis
Вызовите его, в то время как обратный вызов вызывает внутреннийresolve(..)
,reject(..)
функция.
В других случаяхvalue
выполнить для параметровpromise
,передачаonResolved
илиonRejected
функция обработчика.
Фактически,Promise A+规范
ОпределенныйPromise Resolution Procedure
Технологический поток используется для обработкиthen(..)
ЗарегистрированоonResolved
илиonRejected
вызовите возвращаемое значение сthen
вновь сгенерированныйpromise
отношения между. Но учитываяfn
вызов внутри функцииresolve(..)
доходность и текущийpromise
Значение по-прежнему имеет ту же связь, логику и записывает тот же модуль.
функция отклонения
Promise
внутренний частный методreject
В сравнении сresolve
Логика намного проще. Следующим образом:
function reject (promise, reason) {
// 非 pending 状态不可变
if (promise._state !== 0) return;
// 改变 promise 内部状态为 `rejected`
promise._state = 2;
promise._value = reason;
// 判断是否存在 then(..) 注册回调处理
if (promise._deferreds.length !== 0) {
// 异步执行回调函数
for (var i = 0; i < promise._deferreds.length; i++) {
handleResolved(promise, promise._deferreds[i]);
}
promise._deferreds = [];
}
}
функция handleResolved
пониматьPromise
Конструктор,then
функции и внутренниеresolve
а такжеreject
Реализация функции, вы обнаружите, что все выполнения обратного вызова вызываются единообразноhandleResolved
функция, чтоhandleResolved
Что вы делали и на что следует обратить внимание?
handleResolved
Функция будет основана наpromise
Вызов судебного решения текущего состоянияonResolved
,onRejected
,иметь дело сthen(..)
Зарегистрируйте обратный вызов пустым и поддерживайте цепочкуthen(..)
Последующие вызовы функции. Конкретная реализация выглядит следующим образом:
function handleResolved (promise, deferred) {
// 异步执行注册回调
asyncFn(function () {
var cb = promise._state === 1 ?
deferred.onResolved : deferred.onRejected;
// 传递注册回调函数为空情况
if (cb === null) {
if (promise._state === 1) {
resolve(deferred.promise, promise._value);
} else {
reject(deferred.promise, promise._value);
}
return;
}
// 执行注册回调操作
try {
var res = cb(promise._value);
} catch (err) {
reject(deferred.promise, err);
}
// 处理链式 then(..) 注册处理函数调用
resolve(deferred.promise, res);
});
}
Функция обратного вызова регистрации специальной обработкиcb
пустой, как в примере ниже. Определить текущий обратный вызовcb
Когда пусто, используйтеdeferred.promise
как текущийpromise
комбинироватьvalue
Вызовите последующую функцию обработки, чтобы продолжить выполнение, и поймите, что значение передается через пустую функцию обработки.
Promise.resolve(233)
.then()
.then(function (value) {
console.log(value)
})
оthen
Цепные вызовы, давайте кратко поговорим об этом. выполнитьthen
Сцепленные вызовы функций, просто должны быть вPromise.prototype.then(..)
вернуть новый в функции-обработчикеpromise
Просто пример. Но помимо этого необходимо вызватьthen
Зарегистрированный обработчик обратного вызова. Такие какhandleResolved
Последнее предложение функцииresolve(deferred.promise, res)
показано.
Почему зарегистрированная функция обратного вызова затем выполняется асинхронно
Вот ответ на вопрос, заданный в начале,then
ЗарегистрированоonResolved
,onRejected
Почему функции должны выполняться асинхронно? Давайте посмотрим на пример кода.
var a = 1;
promise1.then(function (value) {
a = 2;
})
console.log(a)
Неизвестно, выполняет ли promise1 синхронную или асинхронную операцию внутри себя. если не указаноthen
Если обратный вызов зарегистрирован для асинхронного выполнения, здесь может быть два значения для вывода a. a === 2, когда promise1 работает синхронно внутри, и a === 1 при выполнении асинхронных операций наоборот. Чтобы замаскировать неопределенность зависимости от внешних факторов, в спецификации указываетсяonFulfilled
а такжеonRejected
Способ выполняется асинхронно.
обещание внутренней ошибки или исключения
еслиpromise
одеялоrejected
, обратный вызов отклонения вызывается с переданной причиной отклонения. например, вPromise
при создании(fn
Когда возникает исключение, оно будет перехвачено и вызваноonRejected
.
Но есть еще деталь, еслиPromise
позвони, когда закончишьonResolved
Что делать, если при просмотре результатов возникает ошибка исключения? Обратите внимание, что в это времяonRejected
не будет запускаться для выполнения, потому чтоonResolved
Внутреннее исключение не изменяет текущийpromise
статус (все ещеresolved
), но изменитьthen
вернуть новый вpromise
Статусrejected
. Исключение не теряется, но обработчик ошибок не вызывается.
Как с этим бороться?Ecmascript
Спецификации определеныPromise.prototype.catch
метод, если вы правыonResolved
Нет уверенности в процессе или есть нештатный случай, лучшеthen
вызов после функцииcatch
Метод ловит исключение и обрабатывает итоговую строку.
Реализация метода, связанного с обещанием
чек об оплатеPromise
Связанные документы или книги, вы также найдетеPromise
Связанные полезные API:Promise.race
,Promise.all
,Promise.resolve
,Promise.reject
. Прямо здесьPromise.race
Показана реализация метода, а остальное можно реализовать самостоятельно.
Promise.race = function (values) {
return new Promise(function (resolve, reject) {
values.forEach(function(value) {
Promise.resolve(value).then(resolve, reject);
});
});
};
Эпилог
Написано здесь, ядроPromise
Реализация также постепенно завершается,Promise
Внутренние детали также описаны в тексте или в коде. Ввиду ограниченных возможностей автора, дляpromise
Внутренняя реализация еще не достигла уровня Paoding Jie Niu, а некоторые места были упомянуты одним махом, что может вызвать сомнения в сердцах читателей. Для неясных мест рекомендуется прочитать его дважды или обратиться к книгам, чтобы понять.
Если вы сможете немного выиграть после прочтения эссе, это можно считать достижением первоначального замысла автора, и все будут расти вместе. В итоге автор не идеален, и предложения в тексте не плавные или слова не выразительные.Надеюсь на понимание. Если у вас есть какие-либо вопросы или ошибки в этой статье, вы можете исправить их, заранее спасибо.
приложение
Справочная документация
Справочная литература
event loop
-
Difference between microtask and macrotask within an event loop context
-
Глубокое погружение в проблемы синхронизации с циклом событий и рендерингом в браузере
Реклама, прошу обратить внимание на публичный номер автора