Мысли о порядке вызовов с цепочкой обещаний

JavaScript Promise

image

предисловие

За последний месяц я не писал в блог, а назад друг задал мне вопрос о порядке выполнения связанных звонков Обещание

image

С моим знанием исходного кода Promise, может ли этот вопрос поставить меня в тупик?

image

Тогда конечно я ответил неправильно

image

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

вопрос

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

new Promise((resolve, reject) => {
  console.log("log: 外部promise");
  resolve();
})
  .then(() => {
    console.log("log: 外部第一个then");
    new Promise((resolve, reject) => {
      console.log("log: 内部promise");
      resolve();
    })
      .then(() => {
        console.log("log: 内部第一个then");
      })
      .then(() => {
        console.log("log: 内部第二个then");
      });
  })
  .then(() => {
    console.log("log: 外部第二个then");
  });
  
// log: 外部promise
// log: 外部第一个then
// log: 内部promise
// log: 内部第一个then
// log: 外部第二个then
// log: 内部第二个then

Его тестовый сайт не ограничивается самим промисом, а также проверяет порядок между вызовами цепочки PROMISE.Прежде чем начать разрешение, вы должны сначала очистить принцип Promise to chain.

promise 的 then/catch 方法执行后会也返回一个 promise

Здесь сначала дается заключение, а потом анализируется проблема

Вывод 1

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

Выполнение метода then является синхронным, а обратный вызов в then — асинхронным.

new Promise((resolve, reject) => {
  resolve();
}).then(() => {
  console.log("log: 外部第一个then");
});

Функция, переданная для создания экземпляра Promise, выполняется синхронно, и сам метод then также выполняется.СинхронизироватьВыполняется, но обратный вызов в ТОГДА сначала ставится в очередь микрострунов, после выполнения синхронной задачи, затем снова вынимается, другими словами, только обратный вызов является асинхронным

В то же время, когда метод тогда выполняется синхронно, суждение будет сделано:

  • Если предыдущее обещание уже разрешено, обратный вызов будет немедленно помещен в очередь микрозадач (но обратный вызов все равно будет выполняться после завершения всех задач синхронизации).
  • Если предыдущее обещание находится в ожидании, обратный вызов будет сохранен внутри обещания, и обратный вызов не будет помещен в очередь микрозадач, пока обещание не будет разрешено.

Вывод 2

Когда обещание разрешено, оно будет проходить через все обратные вызовы, ранее зарегистрированные для этого обещания, и по очереди помещать их в очередь микрозадач.

как понять通过 then 给这个 promise 注册的所有回调, рассмотрим следующий случай

let p = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000);
});
p.then(() => {
  console.log("log: 外部第一个then");
});
p.then(() => {
  console.log("log: 外部第二个then");
});
p.then(() => {
  console.log("log: 外部第三个then");
});

Переменная p будет разрешена через 1 секунду, но для нее зарегистрировано 3 обратных вызова через метод then до разрешения.В настоящее время эти 3 обратных вызоване будут выполняться или помещаться в очередь микрозадач, они будут храниться внутри p(При написании промисов от руки эти обратные вызовы будут помещены в массив, сохраненный внутри промиса), и после того, как p будет разрешено, поочередно запихнуть эти три обратных вызова в очередь микрозадачи.Если задачи синхронизации в это время нет, они будут взяты выведены и выполнены один за другим

Есть еще несколько вещей, на которые стоит обратить внимание:

  1. Для обычных обещаний, когда выполняется функция разрешения, разрешается состояние обещания.

Функция разрешения — это первый параметр, передаваемый функции при создании экземпляра промиса.

new Promise(resolve => {
  resolve();
});

Его роль заключается не только в том, чтобы изменить текущий промис с ожидающего на решенный, но и в том, чтобы пройти через все обратные вызовы, ранее зарегистрированные для этого промиса, и поместить их в очередь микрозадач.Многие думают, что метод then запускает его для сохранения обратного вызова, но на самом деле метод then не запускает обратный вызов и не помещает его в микрозадачу, затем отвечает только за регистрацию обратного вызова, а resolve помещает зарегистрированный обратный вызов в очередь микрозадачи. , который извлекается и выполняется циклом обработки событий

具体的行为可以参考底部链接

  1. Для промиса, возвращаемого методом then, нет функции разрешения, вместо этого пока код обратного вызова в then выполняется иполучить синхронное возвращаемое значение, обещание, возвращаемое this, затем разрешается

同步返回值значит другими словами

Если обратный вызов then возвращает промис, то промис, возвращенный then, ожидает разрешения этого промиса.После этого поместите задачу в очередь микрозадач, и функция этой задачи — разрешить промис, возвращенный методом then обратного вызова.

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

Рассмотрим следующий пример:

new Promise(resolve => {
  resolve();
})
  .then(() => {
    new Promise(resolve => {
      resolve();
    })
      .then(() => {
        console.log("log: 内部第一个then");
        return Promise.resolve();
      })
      .then(() => console.log("log: 内部第二个then"));
  })
  .then(() => console.log("log: 外部第二个then"));
  
  // log: 内部第一个then
  // log: 外部第二个then
  // log: 内部第二个then

Здесь первый метод then возвращает промис (зеленое поле).Согласно заключению второго пункта выше, когда обещание в зеленом поле будет разрешено, обратный вызов в желтом поле будет помещен в очередь микрозадачи, а затем зеленое поле будет выполнено.

Основная тема: обратный вызов зеленого ящика

Очередь микрозадач: пуста

Обратный вызов в зеленом поле объявляет обещание, а затем вызывается метод then (фиолетовое поле).Поскольку предыдущее обещание уже разрешено, обратный вызов в фиолетовом поле будет немедленно помещен в очередь микрозадач.

Основная тема: обратный вызов зеленого ящика

Очередь микрозадач: обратный вызов обещания фиолетового ящика

Затем выполните второй метод then (красное поле).Поскольку обратный вызов в фиолетовом поле все еще не выполняется в очереди микрозадач, фиолетовое поле все еще находится в состоянии ожидания, поэтому обратный вызов в красном поле будет зарегистрирован до тех пор, пока фиолетовый box разрешается. , прежде чем помещать в очередь микрозадач

Основная тема: обратный вызов зеленого ящика

Очередь микрозадач: обратный вызов обещания фиолетового ящика

Вся логика синхронизации обратного вызова в зеленом поле выполняется, а возвращаемое значение не определено.В это время обещание в зеленом поле разрешается, а обещание в желтом поле помещается в микрозадачу.

основной поток: пустой

Очередь микрозадач: обратный вызов обещания фиолетового ящика, обратный вызов обещания желтого ящика

Основной поток простаивает, а выполнение задач из очереди микрозадач выносится по очереди

Основная тема: обратный вызов обещания фиолетовой коробки

Очередь микрозадач: обратный вызов обещания желтого ящика

Далее это точка, после выполнения обратного вызова обещания фиолетового ящика напечатайтеlog: 内部第一个 then, и возвращает промис с разрешенным состоянием. В это время обещание фиолетового прямоугольника не будет разрешено немедленно. Вместо этого в очередь микрозадач будет добавлена ​​дополнительная микрозадача. Когда эта микрозадача будет выполнена, обещание фиолетового прямоугольника будет разрешено .

Если у вас есть сомнения по поводу этой дополнительной микрозадачи, вы можете увидеть эту часть исходного кода внизу.

основной поток: пустой

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

В это время основной поток снова простаивает, и на выполнение выносится первая задача в очереди микрозадач, то есть обратный вызов промиса в желтом поле, и печатаетсяlog: 外部第二个 then

основной поток: пустой

Очередь микрозадач: задача выполнения обещания фиолетового ящика

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

основной поток: пустой

Микро-очередь задач: обещание обратного вызова красного ящика

Затем выньте задачу, выполните обратный вызов обещания красного поля, напечатайтеlog: 内部第二个 then, все кончено

Разобрать проблему

Проблема, которая поставила меня в тупик, не так сложна, как описанная выше.Далее давайте разберем эту проблему в сочетании с двумя выводами выше (рекомендуется разделить экран, сравнить код случая в главе проблемы для просмотра анализа)

Во-первых, когда создается экземпляр промиса, функция выполняется синхронно, выводяlog: 外部promise, а затем выполнить функцию разрешения, чтобы изменить обещание на разрешенное, но поскольку метод then не был выполнен в это время, ничего не произойдет при обходе всех обратных вызовов, зарегистрированных методом then (Вывод 2, первый пункт)

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

Основной поток: сначала внешний, потом внешний, затем

Очередь микрозадач: пуста

Затем выполнить первый внешний then (далее: external 1then), так как предыдущий промис разрешился, колбэк сразу ставится в очередь микрозадачи (вывод 1)

Основная нить: снаружи 2тогда

Очередь микрозадач: Обратный вызов от 1затем

Однако, поскольку callback в это время не был выполнен, промис, возвращаемый внешним 1then, все еще находится в состоянии ожидания (вывод 2, второй пункт), а внешний 2then будет продолжать выполняться синхронно. находится в состоянии ожидания, обратный вызов внешнего 2тогда не будет помещен в очередь микрозадач, не будет выполняться (вывод 2 случая)

основной поток: пустой

Очередь микрозадач: Обратный вызов от 1затем

Когда основной поток завершит выполнение, выполните микрозадачу, то есть обратный вызов внешнего 1then, и напечатайте первый в обратном вызовеlog: 外部第一个then

Затем создайте экземпляр внутреннего обещания, выполните функцию при создании экземпляра, напечатайтеlog: 内部promise, затем выполнить функцию разрешения (вывод 1), а затем выполнить первый внутренний затем (далее: внутренний 1затем), так как предыдущий промис был разрешен, обратный вызов ставится в очередь микрозадачи (вывод 1)

Основная нить: в течение 2тогда

Очередь микрозадач: обратный вызов в пределах 1затем

由于正在执行外1then 的回调,所以外1then 返回的 promise 仍是 pending 状态,外2then 的回调仍不会被注册也不会被执行

Затем синхронно выполните внутреннее 2then.Поскольку предыдущий промис (обещание, возвращенное внутренним 1then) находится в состоянии ожидания (поскольку обратный вызов внутреннего 1then находится в очереди микрозадачи и еще не выполнен), обратный вызов внутренний 2then совпадает с обратным вызовом внешнего 2then.Нет регистрации, нет выполнения (случай Заключения 2)

основной поток: пустой

Очередь микрозадач: обратный вызов в пределах 1затем

В это время выполняются все обратные вызовы внешнего 1, а состояние промиса, возвращенного внешним 1, затем изменяется с pending (ожидание) на выполненное (второй пункт вывода 2), В то же время он проходит все обратные вызовы ранее зарегистрировались для этого промиса через then, и кладут свои коллбэки в микро В очередь задач (вывод 2), то есть callback помещают во внешний 2then

основной поток: пустой

Очередь микрозадач: обратный вызов для внутреннего 1затем, обратный вызов для внешнего 2затем

В этот момент выполняется логика основного потока, и первая микрозадача выносится на выполнение.

Основной поток: обратный вызов в пределах 1then

Очередь микрозадач: снаружи 2, затем обратный вызов

Выполните печать обратного вызова в течение 1, затемlog: 内部第一个then, после выполнения обратного вызова обещание, возвращаемое внутренним 1, затем изменяется с ожидающего на выполненное (заключение 2, второй пункт), и проходит через все обратные вызовы, ранее зарегистрированные для этого промиса, и помещает их обратные вызовы в очередь микрозадачи (заключение 2 ), то есть callback помещается внутри 2then

основной поток: пустой

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

Выполните печать обратного вызова снаружи 2, затемlog: 外部第二个then, обратный вызов выполняется, промис, возвращаемый внешним 2then, меняется с pending (ожидающий) на выполненный (второй пункт вывода 2), и он проходит через then все обратные вызовы, ранее зарегистрированные для этого промиса, и ставит их в очередь микрозадач (вывод 2)

В это время, поскольку обещание, возвращенное внешним 2, не имеет дальнейших цепочек вызовов, задача основного потока завершается.

основной поток: пустой

Очередь микрозадач: обратный вызов для 2, затем

Затем выньте микрозадачу и выполните печать обратного вызова 2, затемlog: 内部第二个then, состояние промиса, возвращенного 2then, становится разрешенным (второй пункт вывода 2), и он проходит через все обратные вызовы, зарегистрированные для этого промиса, через then (ничего), и все кончено.

использованная литература

чье-то обещание