Отказ от ответственности: эта статья является первой подписанной статьей Nuggets, и ее перепечатка без разрешения запрещена.
написать впереди
Обещания очень просты в использовании, и рабочий механизм JavaScript не сложен, но после того, как рабочий механизм связан с обещаниями, он часто может заставить людей смущаться. Если это так, то есть эта статья может помочь вам решить вашу путаницу .
Несколько дней назад несколько друзей прочитали то, что я написал давным-давно"Жесткий JS" время от времени, механизм работы JSПосле этого личное сообщение задало мне вопрос, в котором говорилось, что механизм работы понятен, но он включает в себя различные запутанные поведения Promise (различные вложенные выходные данные, связанные выходные данные и т. д.).then
д.), до сих пор не понимаю. На самом деле, ядро этой статьи — это просто концепция механизма работы, а мелкие партнеры, которые не уверены в запутанном поведении Promise, вызваны их непониманием общего механизма реализации Promise.
Если вы не знаете, понимаете ли вы это, вы можете пропустить несколько последних подзаголовков и посмотреть, сможете ли вы правильно ответить на эти вопросы.
По просьбам читателей данная статья является прической и дополнением к механизму работы Promise+.Важно собственно бой.Перечислено несколько распространенных типов вопросов, связанных с порядком вывода Promise, охватывающих практически все типы Promise сложных Короче говоря, есть только одна цель: полностью понять различные запутанные поведения рабочего механизма Promise+.
Краткое введение в рабочий механизм JS
Прежде чем мы начнем, все же необходимо кратко представить механизм работы JS.
В JavaScript есть концепция синхронных/асинхронных задач, и синхронные задачи выполняются в основном потоке, который формирует执行栈
, вне основного потока, поток, инициирующий событие, управляет任务队列
, пока асинхронная задача имеет работающий результат, в任务队列
Поместите в него обратный вызов события. однажды执行栈
После выполнения всех задач синхронизации в任务队列
, добавить исполняемую асинхронную задачу (обратный вызов события в очереди задач, пока в очереди задач есть обратный вызов события, это означает, что она может быть выполнена) в стек выполнения и начать выполнение.
Синхронные/асинхронные задачи — это в широком смысле, в то же время в JavaScript есть более подробные понятия макрозадачи и микрозадачи, мы можем рассматривать код, выполняемый каждым стеком выполнения, как макрозадачу (в т.ч. Каждый раз при получении события обратного вызова из очереди событий и помещается в стек выполнения для выполнения), каждая задача макроса будет выполняться от начала до конца, и больше ничего выполняться не будет. В асинхронных задачах некоторые специальные задачи называются микрозадачами, которые выполняются сразу после выполнения текущей макрозадачи.
Наиболее распространенные микрозадачи:
- process.nextTick-Node
- Promise.then
- catch
- finally
- Object.observe
- MutationObserver
- queueMicrotask
- ...
Короче, полный JS-код, браузер начнет выполнение общего скрипта (как первая задача макроса), весь код разбит на同步任务
,异步任务
Две части:
- Синхронные задачи напрямую входят в стек выполнения основного потока и выполняются последовательно.Асинхронные задачи далее делятся на обычные асинхронные задачи (также макрозадачи) и специальные асинхронные задачи (т.е. микрозадачи);
- Обычные асинхронные задачи и т. д. имеют текущие результаты, и их обратные вызовы войдут в управление потоками, инициируемыми событиями.
任务队列
(можно понимать как очередь задач макроса); - Обратный вызов специальной асинхронной задачи, то есть микрозадачи, сразу попадет в очередь микрозадач;
- Когда задачи в основном потоке выполняются, то есть когда основной поток пуст, будет проверяться очередь микрозадач, если задачи есть, то все они будут выполняться, если нет, то следующая задача макроса выполнено (управление потоками по событию
任务队列
середина);
Вышеупомянутый процесс будет продолжать повторяться, это цикл событий, цикл событий.
Если рендеринг добавлен в браузер, сначала выполняется макрозадача, затем выполняются все текущие микрозадачи, затем запускается рендеринг, затем выполняется следующая макрозадача и так далее.
Краткий обзор, подробности смотрите 👉«Хардкорный JS» понимает механизм работы JS одновременно
Обещают рукописную реализацию
Поскольку некоторые внутренние рабочие механизмы Promise будут задействованы позже, пожалуйста, терпеливо прочитайте эту часть написанного от руки Promise, не слишком много, только основную часть, которая также очень проста, просто посмотрите на идеи.
Promises/A+
Стандарт Promises/A+ — это открытый, надежный и универсальный стандарт обещаний JavaScript, сформулированный разработчиками для справки. Многие сторонние библиотеки Promise реализованы в соответствии со стандартом Promises/A+.
Итак, для этой реализации мы строго следуем стандарту Promises/A+, в том числе используем тестовый пакет, предоставленный сообществом открытого исходного кода, для тестирования после завершения. Если тест пройден, этого достаточно, чтобы доказать, что код соответствует стандарту Promises/A+, является законным и может быть предоставлен в Интернете для использования другими.
Способ возведения основного фундамента
- Обещания имеют три состояния: Ожидание, Решено/Выполнено и Отклонено.
- Promise — это конструктор, который передает функцию в качестве обработчика при создании экземпляра Promise.
- Функция-обработчик имеет два параметра (разрешить и отклонить), чтобы преобразовать результат в состояние успеха и состояние отказа соответственно.
- Если объект Promise успешно выполнен, должен быть результат, который передается через разрешение, а в случае неудачи причина сбоя передается через отклонение.
- Прототип Promise определяет метод then.
Затем, в соответствии с нашими известными выше требованиями, мы можем написать базовую структуру (тысячи способов, вы также можете использовать класс, если вам нравится класс).
function Promise(executor) {
// 状态描述 pending resolved rejected
this.state = "pending"
// 成功结果
this.value = undefined
// 失败原因
this.reason = undefined
function resolve(value) {}
function reject(reason) {}
}
Promise.prototype.then = function (onFulfilled, onRejected) {}
Как показано выше, мы создали конструктор Promise,state
Свойства содержат состояние объекта Promise, используйтеvalue
Свойство содержит результат успешного выполнения объекта Promise, и используется причина сбоя.reason
Свойства сохраняются, и эти имена полностью соответствуют стандарту Promises/A+.
Затем создаем в конструктореresolve
а такжеreject
два метода, затем создал один на прототипе конструктораthen
способ, готовый к использованию.
Инициализировать экземпляр исполнителя для немедленного выполнения
Мы знаем, что при создании экземпляра Promise функция-обработчикexecutor
выполняется сразу, поэтому меняем код:
function Promise(executor) {
this.state = "pending"
this.value = undefined
this.reason = undefined
// 让其处理器函数立即执行
try {
executor(resolve, reject)
} catch (err) {
reject(err)
}
function resolve(value) {}
function reject(reason) {}
}
реализация обратного вызова разрешить и отклонить
Спецификация Promises/A+ предусматривает, что когда объект Promise переходит из состояния ожидания в успешное состояниеresolved
или состояние отказаrejected
Состояние не может быть изменено снова после успеха или неудачи, то есть состояние не может быть обновлено после того, как успех или неудача были заморожены.
Поэтому нам нужно судить, когда мы обновляем состояние, является ли текущее состояние состоянием ожидания.pending
можно обновить, чтобы мы могли улучшитьresolve
а такжеreject
метод.
let _this = this
function resolve(value) {
if (_this.state === "pending") {
_this.value = value
_this.state = "resolved"
}
}
function reject(reason) {
if (_this.state === "pending") {
_this.reason = reason
_this.state = "rejected"
}
}
Как показано выше, сначала мы используем переменную внутри конструктора Promise._this
управляемый конструкторthis
.
Тогда мыresolve
а такжеreject
В функцию добавлено суждение, потому что операция изменения состояния может быть выполнена только в том случае, если текущее состояние находится в состоянии ожидания.
При этом как успешный результат, так и причина неудачи сохраняются в соответствующих атрибутах.
Затем установите свойство состояния в обновленное состояние.
затем базовая реализация метода
Тогда мы просто реализуемthen
метод.
первыйthen
Метод имеет два обратных вызова, при изменении состояния Promise будет вызываться успех или неудача соответственно.then
Два обратных вызова метода.
Таким образом, реализация метода then выглядит достаточно просто: вы можете вызывать разные callback-функции в зависимости от состояния.
Promise.prototype.then = function (onFulfilled, onRejected) {
if (this.state === "resolved") {
if (typeof onFulfilled === "function") {
onFulfilled(this.value)
}
}
if (this.state === "rejected") {
if (typeof onRejected === "function") {
onRejected(this.reason)
}
}
}
Как показано выше, посколькуonFulfilled & onRejected
Оба параметра не являются обязательными параметрами, поэтому мы оцениваем тип параметра после оценки состояния.Если параметр не является функциональным типом, он не будет выполнен, потому что нефункциональный тип, определенный в спецификации Promises/A+, можно игнорировать.
Давать обещания асинхронно
Написав это, вы можете подумать, а? Промис реализовать несложно, скоро он будет выглядеть вот так, давайте вкратце его протестируем:
let p = new Promise((resolve, reject) => {
resolve(1)
})
p.then((data) => console.log(data)) // 1
Что ж, как и ожидалось, снова попробуем асинхронный код:
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1000);
})
p.then(data => console.log(data)) // 无输出
Проблема в том, что Promise, асинхронное решение, написанное нами, не поддерживает асинхронность. Первоначально он выполнялся через 1000 мс.then
метод, запустите приведенный выше код и обнаружите, что результата нет, в чем проблема?
Функция setTimeout позволяетresolve
Становится асинхронным выполнением, с задержкой, вызовомthen
метод, текущее состояние все еще находится в состоянии ожиданияpending
,then
метод, который не вызываетсяonFulfilled
тоже не звонилonRejected
.
Причина была выяснена, и мы начали трансформироваться. мы можем выполнитьthen
Если метод все еще ждетpending
, callback-функция временно хранится в очереди (то есть массиве), а при изменении состояния вынимается и выполняется из массива по очереди.
Имея идею, давайте реализуем ее:
Во-первых, нам нужно добавить в конструктор два массива типа Array для хранения функций обратного вызова для успешного и неудачного выполнения.
function Promise(executor) {
let _this = this
this.state = "pending"
this.value = undefined
this.reason = undefined
// 保存成功回调
this.onResolvedCallbacks = []
// 保存失败回调
this.onRejectedCallbacks = []
// ...
}
нам еще нужно улучшитьthen
метод, вthen
Когда метод выполняется, если состояние находится в состоянии ожидания, его функция обратного вызова сохраняется в соответствующем массиве.
Promise.prototype.then = function (onFulfilled, onRejected) {
// 新增等待态判断,此时异步代码还未走完,回调入数组队列
if (this.state === "pending") {
if (typeof onFulfilled === "function") {
this.onResolvedCallbacks.push(onFulfilled)
}
if (typeof onRejected === "function") {
this.onRejectedCallbacks.push(onRejected)
}
}
// 以下为之前代码块
if (this.state === "resolved") {
if (typeof onFulfilled === "function") {
onFulfilled(this.value)
}
}
if (this.state === "rejected") {
if (typeof onRejected === "function") {
onRejected(this.reason)
}
}
}
Как показано выше, мы перепишемthen
метод, в дополнение к оценке состояния успехаresolved
, состояние отказаrejected
, мы добавляем еще одно состояние ожиданияpending
Считается, что когда состояние находится в состоянии ожидания, асинхронный код не был завершен, тогда мы можем сначала сохранить соответствующий обратный вызов в подготовленном массиве.
Теперь последний шаг вот-вот должен быть выполнен, мы находимся вresolve
а такжеreject
метод можно назвать.
function resolve(value) {
if (_this.state === "pending") {
_this.value = value
// 遍历执行成功回调
_this.onResolvedCallbacks.forEach((cb) => cb(value))
_this.state = "resolved"
}
}
function reject(reason) {
if (_this.state === "pending") {
_this.reason = reason
// 遍历执行失败回调
_this.onRejectedCallbacks.forEach((cb) => cb(reason))
_this.state = "rejected"
}
}
На данный момент мы завершили асинхронную поддержку Promise.
Реализация классической цепочки промисов
Обещанияthen
Методы можно вызывать в цепочке, что является одной из сути промисов, и это также более сложное место для реализации.
Сначала мы должны понятьthen
Каковы требования спецификации Promises/A+?then
Определение возвращаемого значения метода и процесс разрешения промисов следующие:
-
первый
then
метод должен возвращатьpromise
объект (акцент) -
если
then
Если метод возвращает общее значение (такое как Number, String и т. д.), используйте это значение, чтобы обернуть его в новый объект Promise и вернуть его. -
если
then
метод неreturn
оператор, он возвращает объект Promise, завернутый в Undefined -
если
then
Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.then
onRejected -
если
then
Если метод не проходит ни в один обратный вызов, он продолжает проходить вниз (проникновение значения) -
если
then
Если в методе возвращается объект Promise, то этот объект имеет преимущественную силу и будет возвращен его результат.
Что ж, на этом наши требования прояснились, и началась реализация кода.
Требование гласит, что еслиthen
Метод не проходит ни в один обратный вызов, он продолжает передаваться, но каждыйthen
возвращает новое обещание вthen
Когда в методе нет обратного вызова, нам нужно продолжать передавать полученное значение вниз. Это на самом деле легко обработать. Нам нужно только превратить его в функцию обратного вызова и вернуть нормальное значение, когда мы решим, что параметр обратного вызова не функция.
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err
}
// ...
}
над намиthen
В реализации в каждый исполняемый файл добавлена проверка типа, является ли параметр функцией, но мы здесьthen
В начале метода проверка выполняется единообразно, поэтому нет необходимости в проверке параметров.
настоящее времяthen
Метод становится:
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err
}
if (this.state === "pending") {
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
if (this.state === "resolved") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.reason)
}
}
так как каждыйthne
вернуть новый Promise, тогда мы сначалаthen
Взамен создайте экземпляр Promise и начните преобразование.
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err
}
let promise2 = new Promise((resolve, reject) => {
if (this.state === "pending") {
this.onResolvedCallbacks.push(onFulfilled)
this.onRejectedCallbacks.push(onRejected)
}
if (this.state === "resolved") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.reason)
}
})
return promise2
}
мы вthen
В методе экземпляр объекта Promise создается и возвращается, и мы помещаем исходный код в функцию-обработчик экземпляра.
Мы помещаем исходный код в функцию-обработчик этого экземпляра.
Затем при каждой функции выполнения используйтеtry..catch
синтаксис, в попыткеresolve
Результат выполнения, в уловеreject
исключение, оригиналthen
В методе есть три логических суждения о разрешении, отклонении и ожидании:
Когда оценивается разрешенное состояние, логика отклонения и разрешения одинакова.
if (this.state === "resolved") {
try {
// 拿到返回值resolve出去
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
// catch捕获异常reject抛出
reject(e)
}
}
Логика ожидающих государственных суждений также аналогична решению. Однако для того, чтобы справиться с асинхронными, мы делаем толкающую операцию здесь, поэтому, когда мы нажимаем, мы можем установить обратный вызов за пределы нанесения Onefulled и onreject обратных вызовов, чтобы выполнить операцию, которая Все js идиомы. Процедуры, не известен.
if (this.state === "pending") {
// push(onFulfilled)
// push(()=>{ onFulfilled() })
// 上面两种执行效果一致,后者可在回调中加一些其他功能,如下
this.onResolvedCallbacks.push(() => {
try {
let x = onFulfilled(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
this.onRejectedCallbacks.push(() => {
try {
let x = onRejected(this.value)
resolve(x)
} catch (e) {
reject(e)
}
})
}
Далее начинаем обработку по предыдущемуthen
Возвращаемое значение метода используется для генерации нового объекта Promise.Эта логика более сложная.Для этого можно извлечь метод из спецификации.Давайте сделаем это.
/**
* 解析then返回值与新Promise对象
* @param {Object} 新的Promise对象,就是我们创建的promise2实例
* @param {*} x 上一个then的返回值
* @param {Function} resolve promise2处理器函数的resolve
* @param {Function} reject promise2处理器函数的reject
*/
function resolvePromise(promise2, x, resolve, reject) {
// ...
}
Давайте проанализируем и улучшим функцию resolvePromise шаг за шагом.
Избегайте циклических ссылок.Когда возвращаемое значение then совпадает с вновь сгенерированным объектом Promise (ссылочный адрес тот же), будет выброшено TypeError:
пример:
let promise2 = p.then((data) => {
return promise2
})
// TypeError: Chaining cycle detected for promise #<Promise>
Если он возвращает свой собственный объект Promise, состояние всегда находится в ожидании, и его больше нельзя разрешить или отклонить, и программа умрет, поэтому с ним нужно разобраться в первую очередь.
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError("请避免Promise循环引用"))
}
}
Определите тип x и обработайте его в зависимости от ситуации:
Когда x — обещание, выполнить его, успех — это успех, неудача — это провал, еслиx
является объектом или функцией, а затем обрабатывается дальше, в противном случае это нормальное значение.
function resolvePromise(promise2, x, resolve, reject) {
if (promise2 === x) {
reject(new TypeError("请避免Promise循环引用"))
}
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 可能是个对象或是函数
} else {
// 是个普通值
resolve(x)
}
}
Если x является объектом, попробуйте применить метод then к объекту.Если в это время будет сообщено об ошибке, promise2 завершится ошибкой.
Уловка здесь для предотвращения ошибок заключается в том, что существует много реализаций Promise, предполагая, что другой человек реализует объект Promise, используяObject.defineProperty()
Выдавая ошибку при получении значения, мы можем предотвратить ошибки в коде.
// resolvePromise方法内部片段
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 可能是个对象或是函数
try {
// 尝试取出then方法引用
let then = x.then
} catch (e) {
reject(e)
}
} else {
// 是个普通值
resolve(x)
}
если объект имеетthen
,а такжеthen
является типом функции, его можно рассматривать как объект Promise, после чего используйтеx
так как это вызывает выполнениеthen
метод.
// resolvePromise方法内部片段
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 可能是个对象或是函数
try {
// 尝试取出then方法引用
let then = x.then
if (typeof then === "function") {
// then是function,那么执行Promise
then.call(
x,
(y) => {
resolve(y)
},
(r) => {
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
// 是个普通值
resolve(x)
}
На этом этапе нам также необходимо рассмотреть ситуацию.Если объект Promise переходит в успешное состояние или объект Promise передается при сбое, он должен продолжать выполняться до тех пор, пока не будет выполнено последнее обещание, например следующее:
Promise.resolve(1).then((data) => {
return new Promise((resolve, reject) => {
// resolve传入的还是Promise
resolve(
new Promise((resolve, reject) => {
resolve(2)
})
)
})
})
Чтобы решить эту ситуацию, мы можем использовать рекурсию и переписать вызов для разрешения для рекурсивного выполнения resolvePromise, чтобы он не завершался до тех пор, пока промис не будет разрешен до нормального значения.
// resolvePromise方法内部片段
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 可能是个对象或是函数
try {
let then = x.then
if (typeof then === "function") {
then.call(
x,
(y) => {
// 递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
reject(e)
}
} else {
// 普通值结束递归
resolve(x)
}
Как определено в спецификации, если вызываются и resolvePromise, и rejectPromise, или один и тот же параметр вызывается несколько раз, первый вызов имеет приоритет, а все последующие вызовы будут игнорироваться.Чтобы сделать только один вызов для успеха и неудачи, мы продолжайте совершенствоваться, установите Укажите вызываемый, чтобы предотвратить множественные вызовы.
// resolvePromise方法内部片段
let called
if (x !== null && (typeof x === "object" || typeof x === "function")) {
// 可能是个对象或是函数
try {
let then = x.then
if (typeof then === "function") {
then.call(
x,
(y) => {
if (called) return
called = true
// 递归调用,传入y若是Promise对象,继续循环
resolvePromise(promise2, y, resolve, reject)
},
(r) => {
if (called) return
called = true
reject(r)
}
)
} else {
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// 普通值结束递归
resolve(x)
}
На данный момент мы достиглиresolvePromise
метод, давайте вызовем его, чтобы реализовать полныйthen
метод, в оригинальном методе-прототипеthen
в насreturn
Создается обещание2, и среди трех суждений о состоянии функции обработчика экземпляраresolve
заменяетсяresolvePromise
метод.
Затем, в это времяthen
Реализация метода завершена?
Конечно пока нет, все мы знаем, что функция процессора в Promise выполняется синхронно, иthen
Метод асинхронный и является микрозазором, но мы все еще достигаем этой синхронизации.
Решить эту проблему на самом деле очень просто, мы можем использоватьqueueMicrotask
метод реализует микрозадачу вthen
используется везде, где выполняется методqueueMicrotask
Его можно превратить в микрозадачу,queueMicrotask
API имеет проблемы с совместимостью.Реализация здесь в большинстве библиотек Promise является прогрессивной стратегией.Проще говоря,есть несколько схем реализации микрозадач,которые идут по порядку вниз.Если они не совместимы,то используются в последнюю очередь.setTimeout
,следующим образом:
queueMicrotask(() => {
try {
let x = onFulfilled(value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
Теперь наше окончательное изданиеthen
метод выполнен
Promise.prototype.then = function (onFulfilled, onRejected) {
onFulfilled =
typeof onFulfilled === "function" ? onFulfilled : (value) => value
onRejected =
typeof onRejected === "function"
? onRejected
: (err) => {
throw err
}
let promise2 = new Promise((resolve, reject) => {
// 等待态判断,此时异步代码还未走完,回调入数组队列
if (this.state === "pending") {
this.onResolvedCallbacks.push(() => {
queueMicrotask(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
this.onRejectedCallbacks.push(() => {
queueMicrotask(() => {
try {
let x = onRejected(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}
if (this.state === "resolved") {
queueMicrotask(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
if (this.state === "rejected") {
queueMicrotask(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2, x, resolve, reject)
} catch (e) {
reject(e)
}
})
}
})
return promise2
}
поймать реализацию
реализует самые сложныеthen
После метода,catch
Реализация очень проста, и вы можете понять ее с первого взгляда.
Promise.prototype.catch = function (onRejected) {
return this.then(null, onRejected)
}
проверка кода
Сообщество с открытым исходным кодом предоставляет пакет для тестирования нашего кода на соответствие Promises/A+:promises-aplus-tests
.
Сначала мы должны предоставить тестовому пакетуdeferred
Крючок для тестирования.
Таким образом, следующий код предотвратит нашеPromise.js
в конце файла.
// promises-aplus-tests测试
Promise.defer = Promise.deferred = function () {
let defer = {}
defer.promise = new Promise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
try {
module.exports = Promise
} catch (e) {}
Далее устанавливаем пакет:
npm install promises-aplus-tests -D
Выполните тест:
npx promises-aplus-tests Promise.js
Подождите некоторое время, если консоль не станет популярной, она успешна и соответствует спецификациям, как показано на рисунке:
Другие разрешения/отклонения/гонки/все и т. д. относительно просты и не будут здесь описываться.
Я выложу адреса различных методов Promise на своей стороне.Если вам интересно почитать код самостоятельно, комментарии очень подробные, и там около 200 строк кода.
На самом деле, рукописная реализация этого Обещания была давным-давно«Hardcore JS» погружается в асинхронные решенияЭто было написано в главе «Обещание» статьи, но мне нужна эта часть, чтобы понять эту статью, поэтому я скопировал ее, сделал небольшую модификацию и провел тест, чтобы доказать, что она не устарела.
Обратите внимание, что вы должны сначала познакомиться с логикой, рукописными, как осознает, о, в противном случае трудно понять следующее, то следующий начал вводить текст.
Вдохновение после почерка
Основная реализация Promise была представлена выше. Какое вдохновение мы получили от приведенного выше кода?
О, оказывается, метод then возвращает совершенно новый объект Promise.
О, оказывается, утверждение о том, что метод then является микрозадачей, неверно, надо сказать, что callback-функция метода then будет выполняться как микрозадача.
О, оказывается, метод then не выполняется до тех пор, пока не разрешится предыдущий объект Promise, он выполняется в начале и возвращает новый Promise, который будет оцениваться по состоянию предыдущего Promise в возвращенном новом Promise.
-
Предыдущее обещание успеха
Fulfilled
Когда обратный вызов метода then напрямую ставится в очередь как микрозадача -
Последнее обещание терпит неудачу
Rejected
Когда обратный вызов метода then напрямую ставится в очередь как микрозадача -
Последнее обещание еще не выполнено
pending
Когда он внутренне заключает обратный вызов метода then в новый массив экземпляров Promise с помощью метода микрозадачи, он не входит в очередь напрямую. Когда предыдущее промис переходит из состояния ожидания в успешное состояние, будет вызываться метод разрешения нового промиса, возвращаемого сам по себе, тем самым вызывая метод в массиве экземпляров нового промиса (то есть возвращаемого нового промиса), в это время метод микрозадачи выполняет обёртку. Функция обратного вызова будет выполнена, то есть помещена в стек.
О, оказывается, возврат промиса в предыдущем промисе отличается от возврата значения напрямую или без его записи.
- В последнем промисе возвращается все, то есть возвращаемое значение его обратного вызова не определено, что то же самое, что и возврат значения напрямую.Когда предыдущее состояние промиса успешно, метод разрешения нового промиса, созданного внутри, будет вызывается, и значение будет передано.
- Если промис возвращается в предыдущем промисе, когда предыдущее состояние промиса успешно,вызовите его метод then для выполнения, получить значение resolve или reject и выйти (обратите внимание, что, поскольку метод then выполняется внутри при возврате промиса, здесь выполняется еще одна микрозадача, но эта микрозадача на самом деле ничего не делает, просто чтобы получить промис, который мы возвращаем. значение)
Смущенный? Не беда, концепт остается концептом, мы говорим падежами.
Выполнение нескольких промисов
new Promise((resolve, reject)=>{
console.log(1);
resolve();
}).then(() => {
console.log(2);
}).then(() =>{
console.log(3);
});
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
}).then(() => {
console.log(40);
});
// 1 2 10 3 20 30 40
Этот вопрос относительно прост, цель состоит в том, чтобы позволить всем, кто знаком с процедурой решения проблем, сначала пройти через весь процесс и понять его позже.
В первую очередь делаем раздельное название всего вопроса, что удобно для последующих пояснений:
- Вся программа имеет два обещания, которые мы обозначаем как
P1、P2
. - Мы записываем обратный вызов, переданный из Promise в P1, как
P1-主
, есть также два тогда метода, которые мы обозначаем какP1-t1、P1-t2
. - В P2 метод resolve в конструкторе Promise напрямую используется для создания успешного экземпляра, за которым следуют четыре метода then, которые мы обозначаем как
P2-t1、P2-t2、P2-t3、P2-t4
.
Анализ, вся программа будет выполняться как макрозадача в первом пакете, а обратный вызов в методе then будет в конечном итоге введен в очередь микрозадач как микрозадача, ожидающая выполнения макрозадачи, которая будет выполнена в последовательность, а некоторые потом методы будут вызваны обратно во время выполнения задачи макроса.В последнем состоянии промиса былоpending
Когда он обернут методом микрозадачи, он сначала сохраняется в соответствующем экземпляре Promise и кэшируется для последующего выполнения.
Описание, обернутое методом микрозадачи, примерно означает следующее:
// 缓存数组
let arr = []
// 微任务方法包裹的回调存入缓存
arr.push(() => {
queueMicrotask(() => {
// 需要作为微任务执行的代码
let x = onFulfilled(this.value)
resolvePromise(promise2, x, resolve, reject)
// ...
})
})
// 只有arr[0]这个函数执行的时候,微任务才会入队
В это время в нашем уме формируется пустая структурная диаграмма, а именно:
Запустите выполнение кода, сначала выполните первую задачу макроса, то есть программу в целом:
- потому что
new Promise
Когда обратный вызов параметра выполняется синхронно, поэтому выполнитеP1-主
Обратный вызов, вывод 1, затем выполнениеresolve
,Будуnew
Экземпляр Promise становится успешнымFulfilled
. -
P1-t1
Метод then начинает выполняться, так как предыдущий промис выполнен успешно, поэтомуP1-t1
Обратный вызов напрямую входит в очередь микрозадач и ожидает выполнения. -
P1-t2
Метод then начинает выполняться из-заP1-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP1-t2
Обратный вызов использует метод микрозадачи для переноса кеша в экземпляр Promise (примечание: экземпляр Promise здесьP1-t1
Новый Promise вернулся, поэтому мы начинаем сP1-t1返
в начале, чтобы указать, какой экземпляр Promise существует). - После завершения выполнения P1 начинается выполнение P2.
- В P2 успешный экземпляр создается напрямую с помощью метода разрешения в конструкторе Promise.
P2-t1
Когда выполняется метод then, потому что он находится в состоянии успехаFulfilled
,такP2-t1
Непосредственно поставить в очередь как микрозадачу и дождаться выполнения. - тогда
P2-t2
Метод then начинает выполняться из-заP2-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t1返
Этот экземпляр обещания. - тогда
P2-t3
Метод then начинает выполняться из-заP2-t2
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t3
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t2返
в этом экземпляре Promise. - тогда
P2-t4
Метод then начинает выполняться из-заP2-t3
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t4
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t3返
в этом экземпляре Promise.
В этот момент макрозадача основной программы завершается, и текущее состояние выполнения программы выглядит следующим образом:
После выполнения макрозадачи следующим шагом является поочередное выполнение задач в очереди микрозадач.
- По порядку, сначала
P1-t1
Выполнить, выход 2, возвращаемое значениеundefined
, то вызоветP1-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP1-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP1-t1返
Метод кэширования экземпляра. -
P1-t1返
В кеш экземпляра помещаются только методы микрозадач.P1-t2
Обратный вызов после выполненияP1-t2
Войдите в очередь микрозадач и дождитесь выполнения, чтобы эта микрозадачаP1-t1
Исполнение заканчивается, удаление из очереди.
Теперь программа работает следующим образом:
Продолжить выполнение метода в очереди микрозадач
-
P2-t1
Выполнить, вывести 10, возвращаемое значениеundefined
, то вызоветP2-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP2-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP2-t1返
Метод кэширования экземпляра -
P2-t1返
В кеш экземпляра помещаются только методы микрозадач.P2-t2
Обратный вызов после выполненияP2-t2
Войдите в очередь микрозадач и дождитесь выполнения, чтобы эта микрозадачаP2-t1
выполнение заканчивается, удаление из очереди
Теперь программа работает следующим образом:
Все та же процедура, затем выполнить задачу очереди микрозадач
-
P1-t2
Выполнить, выход 3, возвращаемое значениеundefined
, то вызоветP1-t2
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP1-t2返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP1-t2返
Метод кеширования экземпляра, так как его нет,P1-t2返
Экземпляры также не имеют кэшированных методов.P1-t2
Выходит из очереди, P1 заканчивается здесь
На данный момент программа работает следующим образом:
- Затем в очередь микрозадач
P2-t2
Выполнение, вывод 20, возвращаемое значениеundefined
, то вызоветP2-t2
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP2-t2返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP2-t2返
Метод кэширования экземпляра -
P2-t2返
В кеш экземпляра помещаются только методы микрозадач.P2-t3
Обратный вызов после выполненияP2-t3
Войдите в очередь микрозадач и дождитесь выполнения, чтобы эта микрозадачаP2-t2
выполнение заканчивается, удаление из очереди
- Следующим шагом является выполнение очереди микрозадач.
P2-t3
, выводит 30 и аналогично,P2-t4
присоединиться к команде,P2-t3
Выйдите из команды, как показано ниже:
- наконец
P2-t4
Execute, вывод 40, завершение удаления из очереди, P2 завершается, и выполнение завершается, как показано на следующем рисунке:
Итак, окончательный вывод выглядит следующим образом:
// 1 2 10 3 20 30 40
На самом деле, для простых типов вопросов мы можем представить в уме очередь микрозадач, а если она более сложная, то ее можно понять по рисунку.
Если этот вопрос Get, то посмотрите вниз. .
Обещание вложенного выполнения
new Promise((resolve, reject)=>{
console.log("1")
resolve()
}).then(()=>{
console.log("2")
new Promise((resolve, reject)=>{
console.log("10")
resolve()
}).then(()=>{
console.log("20")
}).then(()=>{
console.log("30")
})
}).then(()=>{
console.log("3")
})
Как и в вопросе, два новых экземпляра промиса по-прежнему создаются, как и раньше, но два промиса в этом вопросе вложены друг в друга.
Тогда давайте сначала создадим разделенное имя для всего вопроса:
- Вся программа имеет два обещания, которые мы обозначаем как
P1、P2
- Мы записываем обратный вызов, переданный из Promise в P1, как
P1-主
, есть также два тогда метода, которые мы обозначаем какP1-t1、P1-t2
- Мы записываем обратный вызов, переданный из Promise в P2, как
P2-主
, есть также два тогда метода, которые мы обозначаем какP2-t1、P2-t2
По сути, достаточно провести анализ по той же процедуре, сначала вся программа будет выполняться как макрозадача в первом пакете:
- потому что
new Promise
Когда обратный вызов параметра выполняется синхронно, поэтому выполнитеP1-主
Обратный вызов, вывод 1, затем выполнениеresolve
,Будуnew
Экземпляр Promise становится успешнымFulfilled
. -
P1-t1
Метод then начинает выполняться, так как предыдущий промис выполнен успешно, поэтомуP1-t1
Обратный вызов напрямую входит в микрозадачу и ожидает выполнения - тогда
P1-t2
Метод then начинает выполняться из-заP1-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP1-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP1-t1返
в этом экземпляре Promise.
Выполнение макрозадачи завершается, и все микрозадачи в очереди микрозадач выполняются последовательно.
- воплощать в жизнь
P1-t1
, Выведите 2, затем выполнитеP1-t1
P2 в обратном вызове-
P2-主
Является ли синхронный код для прямого выполнения, вывода 10, а затем выполненияresolve
, Р2new
Экземпляр Promise становится успешнымFulfilled
. - воплощать в жизнь
P2-t1
Метод тогда, так как верхнее Обещание успешного состояния, поэтомуP2-t1
Обратный вызов напрямую входит в очередь микрозадач и ожидает выполнения. - воплощать в жизнь
P2-t2
тогдашний метод, посколькуP2-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t1返
в этом экземпляре Promise.
-
-
P1-t1
Когда обратный вызов выполняется, его возвращаемое значение равноundefined
, то вызоветP1-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP1-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP1-t1返
Метод кэширования экземпляра. -
P1-t1返
В инстансе есть инстансы, которые обернуты микрозадачными методамиP1-t2
, который выполняет свой метод микрозадачи,P1-t2
присоединяйтесь, наконецP1-t1
вне команды
Затем выполните очередь микрозадач:
-
P2-t1
Начать выполнение, вывести 20, возвращаемое значениеundefined
, то вызоветP2-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP2-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP2-t1返
Метод кэширования экземпляра. -
P2-t1返
В инстансе есть инстансы, которые обернуты микрозадачными методамиP2-t2
, который выполняет свой метод микрозадачи,P2-t2
присоединяйтесь, наконецP2-t1
вне команды
Текущее состояние программы следующее:
Затем выполните очередь микрозадач:
- воплощать в жизнь
P1-t2
, выход 3,P1-t2
вне команды. - воплощать в жизнь
P2-t2
, выход 30,P2-t2
Вне очереди программа выполняется, как показано ниже
Итак, результат выполнения этой вложенной программы Promise:
// 1 2 10 20 3 30
Вложенный возвращает новое обещание
Базовая версия
Как я упоминал ранее при написании промиса, новый промис также может быть возвращен в методе разрешения или затем экземпляра промиса.Когда возвращается объект промиса, внутренняя обработка отличается от возврата некоторых базовых значений, поэтому давайте посмотрим на это. следующий. Посмотрите на эту ситуацию.
Promise.resolve().then(() => {
console.log(1);
return Promise.resolve(2)
}).then(res => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
}).then(() => {
console.log(40);
})
Точно так же давайте сначала создадим разделенное имя для всего вопроса:
- Вся программа имеет два обещания, которые мы обозначаем как
P1、P2
- P1 использует метод разрешения в конструкторе Promise для создания успешного экземпляра, за которым следуют два метода then, которые мы обозначаем как
P1-t1
,P1-t2
. - P2 использует метод разрешения в конструкторе Promise для создания успешного экземпляра, за которым следуют 4 метода, обозначенные как
P2-t1
,P2-t2
,P2-t3
,P2-t4
.
Во-первых, вся программа будет выполняться как макро-задача первой партии:
- В P1 успешный экземпляр создается напрямую с помощью метода разрешения в конструкторе Promise.
P1-t1
Когда выполняется метод then, потому что он находится в состоянии успехаFulfilled
,такP1-t1
Непосредственно поставить в очередь как микрозадачу и дождаться выполнения. - тогда
P1-t2
Метод then начинает выполняться из-заP1-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP1-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP1-t1返
в этом экземпляре Promise. - В P2 успешный экземпляр также создается с помощью метода разрешения в конструкторе Promise.
P2-t1
Когда выполняется метод then, потому что он находится в состоянии успехаFulfilled
,такP2-t1
Непосредственно поставить в очередь как микрозадачу и дождаться выполнения. - тогда
P2-t2
Метод then начинает выполняться из-заP2-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t1返
в этом экземпляре Promise. - тогда
P2-t3
Метод then начинает выполняться из-заP2-t2
Состояние экземпляра Promise, возвращаемое методом then, по-прежнемуpending
,такP2-t3
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t2返
в этом экземпляре Promise. - тогда
P2-t4
Метод then начинает выполняться из-заP2-t3
Состояние экземпляра Promise, возвращаемое методом then, по-прежнемуpending
,такP2-t4
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t3返
в этом экземпляре Promise.
Текущее состояние программы следующее:
Задание макроса выполняется, и задание в очереди микросмысла запускается по порядку:
- Первый — выполнить
P1-t1
, выход 1, примечание ⚠️⚠️⚠️ ,P1-t1
То, что возвращается в обратном вызове, является объектом Promise.Помните, когда мы писали Promise вручную раньше, результатом возврата был объект Promise? Правильно, мы вызовем метод then входящего объекта Promise, получим его состояние успеха или неудачи и передадим значение. Поскольку мы внутренне принялиPromise.resolve(2)
Затем выполняется метод этого промиса, иPromise.resolve(2)
Это успешное обещание, поэтому после выполнения метода then его обратный вызов также будет ожидать в очереди, которую мы записываем какP1-t1返
обратный звонок, на самом делеP1-t1返
Этот экземпляр PromisePromise.resolve(2).then((res)=>{...})
. -
P1-t1返
Обратный вызов поставлен в очередь из-заP1-t1返
Обратный вызов поставлен в очередь и еще не выполнен, поэтомуP1-t2
Экземпляр Promise, соответствующий этому методу then, все еще ожидаетpending
,такP1-t2
Все равно никаких действий.
Посмотрим на картинку:
Затем запустите выполнение очереди микрозадач вP2-t1
:
-
P2-t1
Обратный вызов выполняется, выводит 10, а возвращаемое значение равноundefined
, то вызоветP2-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP2-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP2-t1返
Метод кэширования экземпляра. -
P2-t1返
В инстансе есть инстансы, которые обернуты микрозадачными методамиP2-t2
, который выполняет свой метод микрозадачи,P2-t2
присоединяйтесь, наконецP2-t1
вне команды
Как показано ниже:
По порядку в очереди микрозадачи, сейчас приступить к выполнениюP1-t1返
Это перезванивает:
-
P1-t1返
Этот обратный вызов передP1-t1
серединаPromise.resolve(2)
Обратный вызов метода then, который вызывается внутри, на самом деле ничего не делает, просто получает состояние успеха через then, а затем передает значение 2 для разрешения, поэтомуP1-t1返
обратный вызов выполняется, нет вывода,P1-t1返
После внутреннего разрешения этого экземпляра Promise состояние меняется на успешное.Fulfilled
и выполнитьP1-t1返
Метод кэширования экземпляра. -
P1-t1返
В инстансе есть инстансы, которые обернуты микрозадачными методамиP1-t2
, который выполняет свой метод микрозадачи,P1-t2
присоединяйтесь, наконецP1-t1返
вне команды.
Как показано ниже:
Последнее такое же, как и раньше:
- Выполнение очереди микрозадач
P2-t2
, выход 20,P2-t3
присоединиться к команде,P2-t2
вне команды. - Выполнение очереди микрозадач
P1-t2
, выход 2,P1-t2
Удаление из очереди, P1 заканчивается. - Затем выполните очередь микрозадач
P2-t3
, выход 30,P2-t4
присоединиться к команде,P2-t3
вне команды. - Выполнение очереди микрозадач
P2-t4
, выходы 40,P2-t4
Удаление из очереди, P2 заканчивается.
Вывод окончательной программы выглядит следующим образом:
// 1 10 20 2 30 40
Вроде гладко, правда?
насcopy
Запустите эту программу в консоли браузера, чтобы увидеть вывод:
// 1 10 20 30 2 40
? ? ? почему это?
Результат вывода в соответствии с нашей рукописной реализацией обещания выше является первым, но результат вывода в браузере следующий. . .
В нашей предыдущей реализации, написанной от руки, при использовании Promise для возврата нового Promise его метод then будет вызываться внутри для создания новой микрозадачи, а его обратный вызов будет поставлен в очередь.Когда очередь микрозадач выполнит этот обратный вызов, она получит входящий вызов. значение обрабатывается, а затем разрешается.
Но в TC39 ECMA 262 SPEC
Promise
Спецификация такова:
Если мы внимательно посмотрим на спецификацию, мы обнаружим, что спецификация очень ясна, что, вероятно, означает, что при разрешении thenable ECMA 262 предусматривает, что это действие должно пройти задание.
NewPromiseResolveThenableJob
Это делается асинхронно, то есть задание фактически выполняет микрозадачу, которая выполняется позже.NewPromiseResolveThenableJob
Когда функция then вызывается снова (аналогично тому, как мы написали Promise от руки выше, если Promise возвращается, метод then этого Promise вызывается внутри), и в это время выполняется другая микрозадача, так что это две микрозадачи.
в Chrome V8Promise.then
В реализации эта спецификация строго соблюдается.Тут следует отметить, что наша рукописная реализация Promise выше следует спецификации Promise/A+, которая является спецификацией ECMA 262, так что то, что мы написали выше, неплохо, но мы опрашиваем или вопросы, выдаваемые этим тестом, по-прежнему основаны на браузере, поэтому ECMA 262 нужно знать, что нам нужно знать только то, что при возврате объекта Promise браузер сгенерирует 2 микрозадачи для его внутренней реализации. нет необходимости очищать исходный код V8, это не имеет большого значения.
Далее давайте объясним эту проблему с нуля в соответствии со стандартом браузера.
Программа возвращается в исходное состояние следующим образом:
Во-первых, вся программа будет выполняться как макро-задача первой партии:
- В P1 успешный экземпляр создается напрямую с помощью метода разрешения в конструкторе Promise.
P1-t1
Когда выполняется метод then, потому что он находится в состоянии успехаFulfilled
,такP1-t1
Непосредственно поставить в очередь как микрозадачу и дождаться выполнения. - тогда
P1-t2
Метод then начинает выполняться из-заP1-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP1-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP1-t1返
в этом экземпляре Promise. - В P2 успешный экземпляр также создается с помощью метода разрешения в конструкторе Promise.
P2-t1
Когда выполняется метод then, потому что он находится в состоянии успехаFulfilled
,такP2-t1
Непосредственно поставить в очередь как микрозадачу и дождаться выполнения. - тогда
P2-t2
Метод then начинает выполняться из-заP2-t1
Обратный вызов все еще находится в очереди, и состояние экземпляра Promise, возвращаемое последним методом then, остается прежним.pending
,такP2-t2
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t1返
в этом экземпляре Promise. - тогда
P2-t3
Метод then начинает выполняться из-заP2-t2
Состояние экземпляра Promise, возвращаемое методом then, по-прежнемуpending
,такP2-t3
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t2返
в этом экземпляре Promise. - тогда
P2-t4
Метод then начинает выполняться из-заP2-t3
Состояние экземпляра Promise, возвращаемое методом then, по-прежнемуpending
,такP2-t4
Обратный вызов использует метод микрозадачи для переноса кеша вP2-t3返
в этом экземпляре Promise.
Текущее состояние программы следующее:
Выполнение макрозадач завершается, а задачи в очереди микрозадач выполняются последовательно:
- Первый — выполнить
P1-t1
, выход 1, в силу следующегоP1-t1
То, что возвращается в обратном вызове, является объектом Promise, поэтому в соответствии со спецификацией создайте микрозадачу, которую мы записываем какPRTJob
присоединиться к команде.
Как показано ниже:
Затем запустите выполнение очереди микрозадач вP2-t1
:
-
P2-t1
Обратный вызов выполняется, выводит 10, а возвращаемое значение равноundefined
, то вызоветP2-t1
Метод разрешения нового экземпляра Promise, возвращенный в этом методе, вернет значениеundefined
Входящий, после выполнения метода разрешения, он будетP2-t1返
Состояние экземпляра изменяется на успешное состояниеFulfilled
и выполнитьP2-t1返
Метод кэширования экземпляра. -
P2-t1返
В инстансе есть инстансы, которые обернуты микрозадачными методамиP2-t2
, который выполняет свой метод микрозадачи,P2-t2
присоединяйтесь, наконецP2-t1
вне команды
Как показано ниже:
По порядку в очереди микрозадач, к запуску выполненияPRTJob
Это перезванивает:
-
PRTJob
вызывается внутри, поэтому нет вывода,PRTJob
В исполнении он должен идтиNewPromiseResolveThenableJob
спецификации, и поскольку метод then вызывается внутри во время выполнения, он будет повторно поставлен в очередь как микрозадача в это время (вторая микрозадача), которую мы записываем какP1-t1返
Перезвоните. -
P1-t1返
Обратный вызов все еще находится в очереди, поэтомуP1-t1
Состояние экземпляра Promise, возвращаемое методом then, по-прежнемуpending
, поэтому последующиеP1-t2
По-прежнему никаких действий не сохраняется в массиве кеша.
Как показано ниже:
- Затем выполните очередь микрозадач
P2-t2
, выход 20,P2-t3
присоединиться к команде,P2-t2
вне команды.
- Затем выполните очередь микрозадач
P1-t1返
Обратный вызов также является внутренним вызовом без вывода. После того, как обратный вызов выполняет метод разрешения экземпляра внутри,P1-t1
Обещание, возвращаемое методом then, равноP1-t1返
Этот экземпляр Promise, наконец, становится успешнымFulfilled
, затем очистите кеш экземпляра,P1-t2
присоединиться к команде,P1-t1返
Отмена очереди обратного вызова.
- Затем выполните очередь микрозадач
P2-t3
, выход 30,P2-t4
присоединиться к команде,P2-t3
вне команды. - Затем выполните очередь микрозадач
P1-t2
, выход 2,P1-t2
Удаление из очереди, выполнение P1 завершается. - Затем выполните очередь микрозадач
P2-t4
, выходы 40,P2-t4
Удаление из очереди, выполнение P2 завершается.
Окончательный результат выполнения программы выглядит следующим образом:
// 1 10 20 30 2 40
Расширенное издание
Небольшой пример выше просто возвращает Promise, давайте попробуем его с then:
Promise.resolve().then(() => {
console.log(1);
return Promise.resolve(2).then(res=>{
return res
});
}).then(res => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
}).then(() => {
console.log(40);
})
После запуска этого кода в консоли браузера мы обнаружили, что вывод:
// 1 10 20 30 2 40
А? Почему порядок вывода после then такой же, как и при отсутствии then, и изменений нет?
Затем попробуйте еще раз, как показано ниже:
Promise.resolve().then(() => {
console.log(1);
return Promise.resolve(2).then(res=>{
return res
}).then(res=>{
return res
})
}).then(res => {
console.log(res)
})
Promise.resolve().then(() => {
console.log(10);
}).then(() => {
console.log(20);
}).then(() => {
console.log(30);
}).then(() => {
console.log(40);
})
Теперь мы возвращаемсяPromise.resolve(2)
Затем следуют два метода, чтобы увидеть результат:
// 1 10 20 30 40 2
А? Результат вывода снова изменился.Видно, что когда возвращается только простой объект Promise, результат вывода метода then после объекта Promise такой же, но когда за возвращенным Promise следуют два или более метода then, это будет влияет на порядок вывода, почему так?
На самом деле это очень просто.Вы также можете нарисовать картинку присоединения к команде по нашей предыдущей процедуре.Мы уже представили простой возвратPromise.resolve(2)
Диаграмма включения и выключения микрозадач программы. Я не буду рисовать для вас подробную картину здесь, скажем так, и напоследок нарисую картину общей очереди микрозадач программы.
Напомним, что в №1 возвращается только одинPromise.resolve(2)
Для программы мы смотрим на ее общую диаграмму очереди микрозадач:
Давайте посмотримPromise.resolve(2).then(res => return res)
программа:
- Из-за добавления тогда, в дополнение к P1 и P2, упомянутым ранее, мы будем
Promise.resolve(2).then(res => return res)
Обозначим его как P3, а дополнительный метод тогда обозначим какP3-t1
.
Кратко опишите порядок включения и выключения каждой микрозадачи, можете проследить и нарисовать на бумаге:
- Основная часть программы выполняется как первая партия задачи макроса.
- Так как P1
Promise.resolve()
, поэтому обещание состояния успеха возвращается напрямую,P1-t1
присоединиться к команде. -
P1-t2
Метод then выполняется, потому что обещание, возвращенное предыдущим методом then, все еще находится в состоянии ожидания.pending
, поэтому кешируйте вP1-t1返
Этот экземпляр Promise ожидает выполнения. - Также в П2
Promise.resolve()
, поэтому обещание состояния успеха возвращается напрямую,P2-t1
присоединиться к команде. - После P2
P2-t2
,P2-t3
,P2-t4
Каждый кэшируется в экземпляре Promise, возвращаемом его предыдущим методом then
Макрозадача завершается, и очередь микрозадач начинает выполняться:
-
P1-t1
выполнить, вывести 1, затем выполнитьreturn Promise.resolve(2).then(...)
,P3-1
присоединиться к команде. - из-за
P1-t1
Возвращаемое значение обратного вызова — это объект Promise, поэтому создайтеPRTJob
присоединиться к команде.P1-t1
Выполнение обратного вызова завершает удаление из очереди. - Затем выполните очередь микрозадач
P2-t1
обратный вызов, вывод 10,P2-t1返
экземпляр становится успешнымFulfilled
,P2-t2
присоединиться к команде. - Затем выполните очередь микрозадач
P3-t1
Перезвоните,P3-t1
Состояние экземпляра Promise, возвращаемое методом then, изменяется на успешное.Fulfilled
, нет вывода, выполнение завершаетсяP3-t1
вне команды. - Затем выполните очередь микрозадач
PRTJob
обратный звонок из-заP3-t1
Состояние возвращенного экземпляра Promise успешноFulfilled
,такPRTJob
При выполнении вызовите метод thenP1-t1返
Обратный вызов прямо в очередь,PRTJob
вне команды. - Затем выполните очередь микрозадач
P2-t2
обратный вызов, вывод 20,P2-t2返
экземпляр становится успешнымFulfilled
,P2-t3
присоединиться к команде,P2-t2
вне команды. - Затем выполните очередь микрозадач
P1-t1返
Перезвоните,P1-t1返
экземпляр становится успешнымFulfilled
,P1-t2
присоединиться к команде,P1-t1返
вне команды. - Затем выполните очередь микрозадач
P2-t3
обратный вызов, вывод 30,P2-t3返
экземпляр становится успешнымFulfilled
,P2-t4
присоединиться к команде,P2-t3
вне команды. - Затем выполните очередь микрозадач
P1-t2
обратный вызов, выход 2,P1-t2
Удаление из очереди, P1 заканчивается. - Затем выполните очередь микрозадач
P2-t4
обратный вызов, вывод 40,P2-t4
Удаление из очереди, P2 заканчивается.
Порядок постановки и удаления из очереди микрозадач всей программы следующий:
увидеть сноваPromise.resolve(2).then(...).then(...)
программа:
- Так как кроме P1 и P2, упомянутых ранее, есть еще два тогда, мы будем
Promise.resolve(2).then(...).then(...)
Обозначенные как P3, мы обозначаем два метода then какP3-t1
,P3-t2
.
Кратко опишите порядок присоединения и удаления из очереди каждой микрозадачи, как и выше, вы можете следовать и рисовать на бумаге:
- Основная часть программы выполняется как первая партия задачи макроса.
- Так как P1
Promise.resolve()
, поэтому обещание состояния успеха возвращается напрямую,P1-t1
присоединиться к команде. -
P1-t2
Метод then выполняется, потому что обещание, возвращенное предыдущим методом then, все еще находится в состоянии ожидания.pending
, поэтому кешируйте вP1-t1返
Этот экземпляр Promise ожидает выполнения. - Также в П2
Promise.resolve()
, поэтому обещание состояния успеха возвращается напрямую,P2-t1
присоединиться к команде. - После P2
P2-t2
,P2-t3
,P2-t4
Каждый кэшируется в экземпляре Promise, возвращаемом его предыдущим методом then
Макрозадача завершается, и очередь микрозадач начинает выполняться:
-
P1-t1
выполнить, вывести 1, затем выполнитьreturn Promise.resolve(2).then(...)
,P3-t1
присоединиться к команде. - из-за
P1-t1
Возвращаемое значение обратного вызова — это объект Promise, поэтому создайтеPRTJob
присоединиться к команде.P1-t1
Выполнение обратного вызова завершает удаление из очереди. - Затем выполните очередь микрозадач
P2-t1
обратный вызов, вывод 10,P2-t1返
экземпляр становится успешнымFulfilled
,P2-t2
присоединиться к команде. - Затем выполните очередь микрозадач
P3-t1
Перезвоните,P3-t1
Состояние экземпляра Promise, возвращаемое методом then, изменяется на успешное.Fulfilled
, нет вывода, выполнение завершаетсяP3-t2
присоединиться к команде,P3-t1
вне команды. - Затем выполните очередь микрозадач
PRTJob
обратный звонок из-заP3-t2
Все еще в очереди, то есть возвращенное состояние экземпляра все еще находится в состоянии ожидания.pending
, такPRTJob
При выполнении метод then вызывающего экземпляра будет сохранен непосредственно в кэше экземпляра в ожиданииP3-t2
После выполнения обратного вызова статус успешенFulfilled
когда звонили,PRTJob
вне команды. - Затем выполните очередь микрозадач
P2-t2
обратный вызов, вывод 20,P2-t2返
экземпляр становится успешнымFulfilled
,P2-t3
присоединиться к команде,P2-t2
вне команды. - Затем выполните
P3-t2
Перезвоните,P3-t2
Состояние обещания, возвращаемое этим методом, изменяется на успешное состояние.Fulfilled
, в это время внутренне вызывается метод then его экземпляра, а вторая микрозадача генерируется, когда промис возвращается, как указано в спецификации.P1-t1返
Очередь обратного вызова. - Затем выполните очередь микрозадач
P2-t3
обратный вызов, вывод 30,P2-t3返
экземпляр становится успешнымFulfilled
,P2-t4
присоединиться к команде,P2-t3
вне команды. - Затем выполните очередь микрозадач
P1-t1返
Перезвоните,P1-t1返
экземпляр становится успешнымFulfilled
,P1-t2
присоединиться к команде,P1-t1返
вне команды. - Затем выполните очередь микрозадач
P2-t4
обратный вызов, вывод 40,P2-t4
Удаление из очереди, P2 заканчивается. - Затем выполните очередь микрозадач
P1-t2
обратный вызов, выход 2,P1-t2
Удаление из очереди, P1 заканчивается.
Порядок постановки в очередь и удаления из очереди микрозадач всей программы следующий:
Что касается того, почему результаты вывода тогда и без этого одинаковы, давайте посмотрим на сравнение порядка постановки и удаления микрозадач из очереди трех программ:
На самом деле, это в основном потому, что then пишется после возвращаемого объекта Promise, потому что последним промисом этого then являетсяPromise.resolve()
, состояние является состоянием успеха, поэтому оно встанет в очередь первым. Возвращает две микрозадачи, вызванные промисом, вторая — вызвать метод then входящего объекта промиса, если состояние экземпляра промиса перед успешным вызовомFulfilled
Вот и все. возвращаться напрямуюPromise.resolve()
Если , его состояние непосредственно является состоянием успехаFulfilled
, и напишите два или более then после возвращенного промиса, тогда вам нужно дождаться, пока последний экземпляр промиса, возвращенный к тому времени, будет успешным, если вы передадите его во внутренний экземпляр промисаFulfilled
время выполнять.
Неважно, если вы не понимаете моего описания, пока вы можете рисовать картинки, просто следуйте нашему распорядку.
async/await+выполнение обещания
Базовая версия
Async/await на самом деле является синтаксическим сахаром для Generator + Promise, но в нем также есть много ям.
Рассмотрим следующий пример:
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
}
async1();
new Promise(resolve => {
console.log(10);
resolve();
}).then(() => {
console.log(20);
}).then(() => {
console.log(30)
}).then(() => {
console.log(40)
})
Этот пример отличается между старой версией браузера и новой версией браузера, в основном внутренней реализацией браузера.
Таким образом, можно легко понять, что приведенный ниже код await будет поставлен в очередь как микрозадача.
Далее разберем пошагово:
- Во-первых, все начинает выполняться как задача макроса.
- бегать
async1()
, функция async1 начинает выполняться, выводит 1, встречает await, выполняетasync2
, output 3, await Приведенный ниже код поставлен в очередь как микрозадача. - Затем выполните
new Promise()
обратный вызов, вывод 10,resolve
Выполнение возвращенного экземпляра Promise изменяет состояние на состояние успехаFulfilled
. - Выполните первый метод then, потому что экземпляр, возвращаемый следующим промисом, является состоянием успеха.
Fulfilled
, поэтому первый вызов метода then напрямую ставится в очередь. - Выполните второй метод then.Поскольку первый вызов метода then все еще не выполняется в очереди, экземпляр, возвращенный последним промисом, все еще находится в состоянии ожидания.
pending
, второй обратный вызов метода then упаковывается и кэшируется в массиве экземпляров методом микрозадачи. - Выполните третий метод then.Поскольку обратный вызов второго метода then все еще не выполняется в очереди, экземпляр, возвращенный последним промисом, все еще находится в состоянии ожидания.
pending
, третий обратный вызов метода then упаковывается и кэшируется в массив экземпляров методом микрозадачи.
В этот момент макрозадача заканчивается, и начинается выполнение задачи очереди микрозадач.
- Сначала выполните обратный вызов следующего кода в методе async1, который сначала поставлен в очередь, выведите 2, а затем удалите из очереди.
- Затем выполните первый затем обратный вызов в очереди, выведите 20, верните неопределенное значение и выполните его внутренне.
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполните кешированный метод в экземпляре, поэтому второй вызов метода then ставится в очередь, а первый метод then удаляется из очереди. - Затем выполните второй затем обратный вызов в очереди, выведите 30, верните undefined и выполните внутренне
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполнить кешированный метод в экземпляре, поэтому третий обратный вызов метода then ставится в очередь, а первый метод then удаляется из очереди. - Затем выполните третий затем обратный вызов в очереди, выведите 40, верните неопределенное значение и выполните его внутренне.
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполнить метод кеша на экземпляре, массив кеша на экземпляре пуст, третий метод then удаляется из очереди, и программа завершается.
Конечный результат:
// 1 3 10 2 20 30 40
Расширенное издание
Простая смена темы может очаровать большую группу людей следующим образом:
async function async1() {
console.log(1);
await async2();
console.log(2);
}
async function async2() {
console.log(3);
return Promise.resolve()
}
async1();
new Promise(resolve => {
console.log(10);
resolve();
}).then(() => {
console.log(20);
}).then(() => {
console.log(30)
}).then(() => {
console.log(40)
})
Как видите, в предыдущем кодеasync2
Функция не пишет return , то есть возвращает undefined, т.к.async
, окончательная функция - вернуть объект обещания со значением undefined, но теперь мы находимся вasync2
Функция возвращает объект Promise. . .
Вывод, порядок изменился:
// 1 3 10 20 30 2 40
Умные друзья, возможно, видели что-то странное.Когда мы говорили о промисе раньше, мы говорили, что если возвращаемое значение является нормальным значением, то внутри промиса разрешается нормально, но если он возвращает новый объект промиса, внутренне, 2 микрозадачи будет сгенерировано.
Для облегчения понимания здесь, на самом деле, вполне возможно следовать этой линии мышления.
теперь мыasync2
Функция возвращает объект Promise, что эквивалентно генерации еще 2 микрозадач, поэтому порядок 2 в выводе сдвигается на 2 бита.
Общий процесс примерно таков:
- Во-первых, все начинает выполняться как задача макроса.
- бегать
async1()
, функция async1 начинает выполняться, выводит 1, встречает await, выполняетasync2
, сначала выведите 3, потому чтоasync2
Возвращается объект Promise, и первая микрозадача, сгенерированная во время синтаксического анализа, ставится в очередь. - Затем выполните
new Promise()
обратный вызов, вывод 10,resolve
Выполнение возвращенного экземпляра Promise изменяет состояние на состояние успехаFulfilled
. - Выполните первый метод then, потому что экземпляр, возвращаемый следующим промисом, является состоянием успеха.
Fulfilled
, поэтому первый вызов метода then напрямую ставится в очередь. - Выполните второй метод then.Поскольку первый вызов метода then все еще не выполняется в очереди, экземпляр, возвращенный последним промисом, все еще находится в состоянии ожидания.
pending
, второй обратный вызов метода then упаковывается и кэшируется в массиве экземпляров методом микрозадачи. - Выполните третий метод then.Поскольку обратный вызов второго метода then все еще не выполняется в очереди, экземпляр, возвращенный последним промисом, все еще находится в состоянии ожидания.
pending
, третий обратный вызов метода then упаковывается и кэшируется в массив экземпляров методом микрозадачи.
В этот момент макрозадача заканчивается, и начинается выполнение задачи очереди микрозадач.
- Сначала выполните
async2
Возвращает первую микрозадачу, сгенерированную при разрешении объекта Promise, без вывода, затем вторая сгенерированная микрозадача ставится в очередь, а первая сгенерированная микрозадача удаляется из очереди. - Затем выполните первый затем обратный вызов в очереди, выведите 20, верните неопределенное значение и выполните его внутренне.
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполните кешированный метод в экземпляре, поэтому второй вызов метода then ставится в очередь, а первый метод then удаляется из очереди. - Далее выполните
async2
возвращает вторую микрозадачу, сгенерированную при разрешении объекта Promise, без вывода, а затемasync1
Приведенный ниже код await в функции ставится в очередь как микрозадача и возвращает вторую микрозадачу, сгенерированную при разрешении объекта Promise. - Затем выполните второй затем обратный вызов в очереди, выведите 30, верните undefined и выполните внутренне
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполнить кешированный метод в экземпляре, поэтому третий обратный вызов метода then ставится в очередь, а первый метод then удаляется из очереди. - Далее выполните
async1
В функции подождите микрозадачу, сгенерированную приведенным ниже кодом, выведите 2, а затем удалите из очереди. - Затем выполните третий затем обратный вызов в очереди, выведите 40, верните неопределенное значение и выполните его внутренне.
resolve(undefined)
Статус возвращенного экземпляра изменен на статус успехаFulfilled
, и выполнить метод кеша на экземпляре, массив кеша на экземпляре пуст, третий метод then удаляется из очереди, и программа завершается.
Конечный результат:
// 1 3 10 20 30 2 40
На самом деле, вы также можетеasync2
возвращается функциейPromise.resolve()
После добавления метода then вы обнаружите, что порядок вывода остается таким же, как указано выше, а добавление двух или более методов then приведет к сдвигу вывода назад на 2. Нашли сходство?
Правильно, это тот же случай, о котором мы упоминали в Promise выше, поэтому я не буду говорить об этом здесь, если вам интересно, вы можете написать и нарисовать его самостоятельно. .
На самом деле, поскольку спецификация меняется, а браузеры постоянно обновляются, порядок выполнения действительно бессмысленный. . . Мы понимаем это, пока мы можем объяснить, почему конечный результат не должен быть слишком обеспокоен. .
Разное выполнение мэшапа
Наконец, есть смешанный тип вопросов для решения сценария нескольких макрозадач + нескольких микрозадач:
new Promise((reslove, reject) => {
setTimeout(() => {
console.log(10);
}, 2000);
setTimeout(() => {
console.log(20);
}, 1000);
reslove();
}).then(() => {
console.log(1);
return new Promise((reslove, reject) => {
console.log('1-1');
setTimeout(() => {
console.log(30);
reslove();
}, 500);
});
}).then(() => {
console.log(2);
return new Promise((reslove, reject) => {
console.log('2-1');
setTimeout(() => {
console.log(40);
reslove();
}, 200);
});
}).then(() => {
console.log(3);
});
Во-первых, сделайте раздельное имя для каждой части программы.
- все в программе
setTimeout
использоватьtimer+定时的ms数字
имя. - Самый внешний промис обозначается как P1,
new Promise
Обратный звонок записывается какP1-主
, следующие три метода then соответственно обозначаются какP1-t1
,P1-t2
,P1-t3
. -
P1-t1
вернулся вnew Promise
Экземпляр записывается как P2, а обратный вызов его параметра экземпляра записывается какP2-主
. -
P1-t2
вернулся вnew Promise
Экземпляр записывается как P3, а обратный вызов его параметра экземпляра записывается какP3-主
.
На картинке выше показано состояние инициализации программы, а также есть дополнительная очередь задач макросов, давайте рассмотрим ее не спеша.
Сначала вся программа выполняется как макрозадача:
-
P1-主
выполнять, сталкиватьсяtimer2000
,setTimeout
Это асинхронная макрозадача, которая передается потоку триггера синхронизации для обработки через поток триггера события и ожидает своего завершения.2000ms
Обратный вызов вызывается обратно в очередь задач макроса, когда он регулярно завершается. Затем выполнить, столкнутьсяtimer1000
,setTimeout
Это асинхронная макрозадача, которая передается потоку триггера синхронизации для обработки через поток триггера события и ожидает своего завершения.1000ms
Обратный вызов вызывается обратно в очередь задач макроса, когда он регулярно завершается. Наконец, выполните метод разрешения, чтобы изменить состояние возвращенного экземпляра Promise на успешное состояние.Fulfilled
. - из-за
new Promise
Метод разрешения был вызван в обратном вызове параметра экземпляра, поэтому возвращенный экземпляр PromiseP1-主返
статус успешенFulfilled
,P1-t1
Когда метод then выполняется, он напрямую попадает в очередь микрозадач. -
P1-t2
из-заP1-t1
Все еще в обратном вызове экземпляр Promise, который он возвращаетP1-t1返
государство ждетpending
,такP1-t2
Обратный вызов упаковывается и сохраняется методом микрозадачи.P1-t1返
Массив кэша экземпляров. -
P1-t3
из-заP1-t2
Обратный вызов еще не был выполнен, экземпляр Promise, который он возвращаетP1-t2返
государство ждетpending
,такP1-t3
Обратный вызов упаковывается и сохраняется методом микрозадачи.P1-t2返
Массив кэша экземпляров.
На данный момент состояние работы программы следующее:
На этом выполнение первого раунда макрозадач завершается и начинается выполнение очереди микрозадач.
- воплощать в жизнь
P1-t1
Обратный вызов, сначала выведите 1, затем выполните обратный вызов параметра экземпляра Promise returnP2-主
, выход1-1
, встретились сноваsetTimeout
, поток триггера события передаст его потоку триггера синхронизации для обработки, ожидая его500ms
Своевременно завершите свой обратный вызов и войдите в очередь задач макроса.Поскольку метод разрешения выполняется в таймере, состояние экземпляра промиса, созданного новым промисом, все еще находится в состоянии ожидания.pending
. - из-за
P1-t1
Конечным результатом является объект Promise, поэтому в соответствии со спецификацией создайте первое задание микрозадачи, которое мы запишем какPRTJob1
в очередь микрозадач. здесьP1-t1
вне команды. - Затем выполните задачи в очереди микрозадач, т.е.
PRTJob1
Обратный вызов, после завершения выполнения начать выполнениеP1-t1
взаменnew Promise
метод then экземпляра (этот метод вернется после выполненияP1-t1返
экземпляр), так какP1-t1
взаменnew Promise
Экземпляр все еще ждетpending
,такP1-t1
взаменnew Promise
Затем обратный вызов метода экземпляра (обозначается какP1-t1返
callback) упаковывается и сохраняется методом микрозадачиP1-t1返
Массив кэша экземпляров.
На данный момент состояние программы следующее:
В это время очередь микрозадач была выполнена, а в очереди макрозадач нет задач.Через 500 мс таймер запускает потокtimer500
После того, как выполнение имеет результат, обратный вызов отправляется в очередь задач макроса.
В это время основной поток простаивает, и вдруг в очереди задач макросов есть задачи, поэтому сразу же берем первую задачу очереди задач макросов и выполняем ее на основном стеке выполнения JS, то есть запускаем выполнение новой задачи макросовtimer500
.
- воплощать в жизнь
timer500
Обратный вызов, вывод 30, а затем выполнение метода разрешения, в это времяP1-t1
взаменnew Promise
Статус экземпляра изменен на статус успехаFulfilled
, и выполнить кэширование в его экземпляре, т.е.P1-t1返
Обратный вызов в очередь микрозадач.
Как показано ниже:
После выполнения макрозадачи выполняются все микрозадачи, созданные текущей макрозадачей.
- Выполнение очереди микрозадач
P1-t1返
обратный вызов, изменениеP1-t1返
Статус экземпляра успешенFulfilled
, выполняет кэширование своих экземпляров, поэтомуP1-t2
в очередь микрозадач. - Затем выполните очередь микрозадач
P1-t2
Коррекция, вывод 2, а затем выполнить возврат приведенных примеров параметров обратного вызова.P3-主
, выход2-1
, встретились сноваsetTimeout
, поток триггера события передаст его потоку триггера синхронизации для обработки, ожидая его200ms
Своевременно завершите свой обратный вызов и войдите в очередь задач макроса.Поскольку метод разрешения выполняется в таймере, состояние экземпляра промиса, созданного новым промисом, все еще находится в состоянии ожидания.pending
. - из-за
P1-t2
Конечным результатом является объект Promise, поэтому в соответствии со спецификацией создайте первое задание микрозадачи, которое мы запишем какPRTJob2
в очередь микрозадач. здесьP1-t2
вне команды. - Затем выполните задачи в очереди микрозадач, т.е.
PRTJob2
Обратный вызов, после завершения выполнения начать выполнениеP1-t2
взаменnew Promise
метод then экземпляра (этот метод вернется после выполненияP1-t2返
экземпляр), так какP1-t2
взаменnew Promise
Экземпляр все еще ждетpending
,такP1-t2
взаменnew Promise
Затем обратный вызов метода экземпляра (обозначается какP1-t2返
callback) упаковывается и сохраняется методом микрозадачиP1-t2返
Массив кэша экземпляров.
На данный момент состояние программы следующее:
В это время очередь микрозадач была выполнена, а в очереди макрозадач нет задач.Через 200 мс таймер запускает потокtimer200
После того, как выполнение имеет результат, обратный вызов отправляется в очередь задач макроса.
В это время основной поток простаивает, и вдруг в очереди задач макросов есть задачи, поэтому сразу же берем первую задачу очереди задач макросов и выполняем ее на основном стеке выполнения JS, то есть запускаем выполнение новой задачи макросовtimer200
.
- воплощать в жизнь
timer200
Обратный вызов, вывод 40, а затем выполнение метода разрешения, в это времяP1-t2
взаменnew Promise
Статус экземпляра изменен на статус успехаFulfilled
, и выполнить кэширование в его экземпляре, т.е.P1-t2返
Обратный вызов в очередь микрозадач.
Как показано ниже:
После выполнения макрозадачи выполняются все микрозадачи, созданные текущей макрозадачей.
- Выполнение очереди микрозадач
P1-t2返
обратный вызов, изменениеP1-t2返
Статус экземпляра успешенFulfilled
, выполняет кэширование своих экземпляров, поэтомуP1-t3
в очередь микрозадач. - Затем выполните очередь микрозадач
P1-t3
Обратный звонок, выход 3. В конце этого раунда выполнения микрозадачи.
На данный момент состояние программы следующее:
Так как в очереди задач макроса нет задач, основной поток в это время простаивает.После (1000мс-500мс-200мс) таймер запускает потокtimer1000
После того, как выполнение имеет результат, обратный вызов отправляется в очередь задач макроса.
В очереди задач макроса есть задачи, поэтому сразу берем первую задачу очереди задач макроса и выполняем ее на основном стеке выполнения JS, то есть запускаем выполнение новой задачи макросаtimer1000
.
- воплощать в жизнь
timer1000
Обратный вызов, вывод 20, микрозадача не генерируется, поэтому этот раунд выполнения заканчивается.
Так как в очереди задач макроса нет задачи, основной поток в это время простаивает.После (2000мс-1000мс) таймер запускает потокtimer2000
После того, как выполнение имеет результат, обратный вызов отправляется в очередь задач макроса.
В очереди задач макроса есть задачи, поэтому сразу берем первую задачу очереди задач макроса и выполняем ее на основном стеке выполнения JS, то есть запускаем выполнение новой задачи макросаtimer2000
.
- воплощать в жизнь
timer2000
Обратный вызов, вывод 10, микрозадача не генерируется, поэтому этот раунд выполнения заканчивается.
На этом вся программа заканчивается, и окончательный вывод:
// 1 1-1 30 2 2-1 40 3 20 10
Гет еще не пришел?
напиши в конце
Итак, вы понимаете? Лучше всего понять это, и не нужно расстраиваться, если вы этого не понимаете.Пока вы понимаете механизм работы JS и основные концепции Promise, вы можете провести точный анализ некоторых простых последовательностей выполнения. Содержание этой статьи бесполезно для фактической разработки, потому что я действительно не могу себе представить, насколько плохо было бы писать такой код на основе сложных последовательностей вызовов в разработке, но все еще есть много компаний, которые задают эти скучные вопросы. во время интервью, так что зная их, с этого момента не беспокойтесь об этих вопросах интервью.
Если вам неясно, оставьте сообщение в области комментариев и добро пожаловать, чтобы указать на ошибки!
Наконец, кодировать слова непросто, а еще сложнее рисовать картинки, лайк, лайк, лайк! Добро пожаловать, чтобы следовать«Хардкор JS»Колонка, Получите больше знаний JS! !