«Hardcore JS» иллюстрирует запутанное поведение Promise | Дополнительный операционный механизм

внешний интерфейс JavaScript опрос
«Hardcore JS» иллюстрирует запутанное поведение Promise | Дополнительный операционный механизм

Отказ от ответственности: эта статья является первой подписанной статьей 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-код, браузер начнет выполнение общего скрипта (как первая задача макроса), весь код разбит на同步任务,异步任务Две части:

  1. Синхронные задачи напрямую входят в стек выполнения основного потока и выполняются последовательно.Асинхронные задачи далее делятся на обычные асинхронные задачи (также макрозадачи) и специальные асинхронные задачи (т.е. микрозадачи);
  2. Обычные асинхронные задачи и т. д. имеют текущие результаты, и их обратные вызовы войдут в управление потоками, инициируемыми событиями.任务队列(можно понимать как очередь задач макроса);
  3. Обратный вызов специальной асинхронной задачи, то есть микрозадачи, сразу попадет в очередь микрозадач;
  4. Когда задачи в основном потоке выполняются, то есть когда основной поток пуст, будет проверяться очередь микрозадач, если задачи есть, то все они будут выполняться, если нет, то следующая задача макроса выполнено (управление потоками по событию任务队列середина);

Вышеупомянутый процесс будет продолжать повторяться, это цикл событий, цикл событий.

Если рендеринг добавлен в браузер, сначала выполняется макрозадача, затем выполняются все текущие микрозадачи, затем запускается рендеринг, затем выполняется следующая макрозадача и так далее.

Краткий обзор, подробности смотрите 👉«Хардкорный 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Если в методе есть исключение, вызовите метод неудачного состояния (отклонить), чтобы перейти к следующему.thenonRejected

  • если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Его можно превратить в микрозадачу,queueMicrotaskAPI имеет проблемы с совместимостью.Реализация здесь в большинстве библиотек 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

Подождите некоторое время, если консоль не станет популярной, она успешна и соответствует спецификациям, как показано на рисунке:

image-20200206222942803

Другие разрешения/отклонения/гонки/все и т. д. относительно просты и не будут здесь описываться.

Я выложу адреса различных методов 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.

В этот момент макрозадача основной программы завершается, и текущее состояние выполнения программы выглядит следующим образом:

image-20210810192049256

После выполнения макрозадачи следующим шагом является поочередное выполнение задач в очереди микрозадач.

  • По порядку, сначала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-t4Execute, вывод 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-t1P2 в обратном вызове
    • 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 SPECPromiseСпецификация такова:

Если мы внимательно посмотрим на спецификацию, мы обнаружим, что спецификация очень ясна, что, вероятно, означает, что при разрешении 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.

Кратко опишите порядок включения и выключения каждой микрозадачи, можете проследить и нарисовать на бумаге:

  • Основная часть программы выполняется как первая партия задачи макроса.
  • Так как P1Promise.resolve(), поэтому обещание состояния успеха возвращается напрямую,P1-t1присоединиться к команде.
  • P1-t2Метод then выполняется, потому что обещание, возвращенное предыдущим методом then, все еще находится в состоянии ожидания.pending, поэтому кешируйте вP1-t1返Этот экземпляр Promise ожидает выполнения.
  • Также в П2Promise.resolve(), поэтому обещание состояния успеха возвращается напрямую,P2-t1присоединиться к команде.
  • После P2P2-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.

Кратко опишите порядок присоединения и удаления из очереди каждой микрозадачи, как и выше, вы можете следовать и рисовать на бумаге:

  • Основная часть программы выполняется как первая партия задачи макроса.
  • Так как P1Promise.resolve(), поэтому обещание состояния успеха возвращается напрямую,P1-t1присоединиться к команде.
  • P1-t2Метод then выполняется, потому что обещание, возвращенное предыдущим методом then, все еще находится в состоянии ожидания.pending, поэтому кешируйте вP1-t1返Этот экземпляр Promise ожидает выполнения.
  • Также в П2Promise.resolve(), поэтому обещание состояния успеха возвращается напрямую,P2-t1присоединиться к команде.
  • После P2P2-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! !