На самом деле это очень интересный вопрос, потому что спецификации Promise / A + позволят первой версии кода выдать тот же вывод, что и вторая версия кода.
Можно было бы отклонить вопрос о том, что реализация Promise может свободно определять последовательность, в которой вызываются асинхронные обратные вызовы. Это верное утверждение, если посмотреть на спецификацию Promise / A +.
Но спецификация EcmaScript для Promises (раздел 25.4 ) является более подробной, чем спецификация Promise / A +, и требует, чтобы «задания» добавлялись в конец соответствующей очереди заданий - что для обещания поселения - это очередь PromiseJobs ( 25.4.1.3.2 и 8.4 ): это определяет конкретный порядок:
Требуемые очереди заданий
[...]
PromiseJobs : Задания, которые являются ответами на выполнение Обещания
[...]
Записи PendingJob из одной очереди заданий всегда инициируются в порядке FIFO
Он также определяет, что resolve(p)
- когда p
является потомственным элементом - сначала помещает в очередь задание, которое выполнит необходимый внутренний вызов метода p.then
. Это не сделано немедленно. Цитировать примечание в спецификации EcmaScript на 25.4.2.2 :
Этот процесс должен выполняться как задание, чтобы гарантировать, что оценка метода then
происходит после завершения оценки любого окружающего кода.
Это утверждение иллюстрируется порядком вывода в следующем фрагменте:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called asynchronously when triggered by resolve(p1)");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
resolve(p1);
console.log("Code that follows is executed synchronously, before p1.then is");
});
Когда мы используем вызов метода p1.then(resolve)
вместо resolve(p1)
, мы получаем обратный порядок:
const p1 = Promise.resolve();
// Wrap the `p1.then` method, so we can log something to the console:
const origThen = p1.then;
p1.then = function(...args) {
console.log("The p1.then method is called synchronously now");
origThen.call(this, ...args);
};
const p2 = new Promise(resolve => {
p1.then(resolve);
console.log("Code that follows is executed synchronously, after p1.then is");
});
Ваш код
Вышесказанное действительно объясняет другой порядок вывода, который вы получаете. Вот как первая версия кода выполняет последовательность действий. Сначала позвольте мне немного переписать это, чтобы у наиболее часто используемых обещаний было имя:
const p1 = Promise.resolve();
const p2 = new Promise((resolve) => resolve(p1));
const p3 = p2.then(() => console.log('after:await'));
const p4 = p1.then(() => console.log('tick:a'));
const p5 = p4.then(() => console.log('tick:b'))
const p6 = p5.then(() => console.log('tick:c'));
Теперь, после того как основной, синхронный код был выполнен до завершения, только p1
имеет разрешенное состояние, и в очереди заданий (очереди микро-задач) присутствуют два задания, одно из которых в результате resolve(p1)
и второй из-за p1.then
:
- Согласно 25.4.2.2 ,
метод
then
для p1
вызывается с передачей ему внутренней функции [[resolve]]
, связанной с p2
. Внутренние элементы p1.then
знают, что p1
разрешен, и помещают в очередь еще одно задание для фактического разрешения p2
!
Обратный вызов с «tick: a» выполняется, и обещание p4 помечается как выполненное, добавляя новое задание в очередь заданий.
Теперь в очереди 2 новых задания, которые обрабатываются в последовательности:
Задание из шага 1 выполнено: p2 теперь разрешен. Это означает, что новое задание ставится в очередь, чтобы фактически вызвать соответствующий then
callback (s)
- Выполнено задание из шага 2: выполняется обратный вызов с «tick: b»
Только позже будет выполнено задание, добавленное на шаге 3, который вызовет обратный вызов с "after: await".
Итак, в заключение. В EcmaScript resolve(p)
, где p
- это доступное значение, включает асинхронное задание, которое само по себе запускает еще одно асинхронное задание для уведомления о выполнении.
Обратному вызову then
, который отличает вторую версию кода, потребуется только одно асинхронное задание для вызова, и, таким образом, это происходит до вывода «tick: b».