Почему новое обещание разрешается раньше с помощью p.then (разрешение), чем с разрешением (p)? - PullRequest
0 голосов
/ 15 января 2019

Разница между кодом № 1 и кодом № 2 заключается в следующем: код № 1 использует resolve(p), а код № 2 использует p.then(()=>resolve()). Я ожидаю, что последовательность вывода будет инвариантной, но они генерируют другую последовательность. Я не могу понять, почему.

Код № 1: resolve(p)

const p = Promise.resolve();

new Promise((resolve) => {
    resolve(p);    // <---
}).then(() => {
    console.log('after:await');
});

p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'))
    .then(() => console.log('tick:c'));

Выход:

tick:a
tick:b
after:await
tick:c

Код № 2: p.then(()=>resolve())

const p = Promise.resolve();

new Promise((resolve) => {
    p.then(()=>resolve());    // <---
}).then(() => {
    console.log('after:await');
});

p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'))
    .then(() => console.log('tick:c'));

Выход:

tick:a
after:await
tick:b
tick:c

Почему порядок вывода отличается?

Ответы [ 4 ]

0 голосов
/ 15 января 2019

На самом деле это очень интересный вопрос, потому что спецификации 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:

  1. Согласно 25.4.2.2 , метод then для p1 вызывается с передачей ему внутренней функции [[resolve]], связанной с p2. Внутренние элементы p1.then знают, что p1 разрешен, и помещают в очередь еще одно задание для фактического разрешения p2!
  2. Обратный вызов с «tick: a» выполняется, и обещание p4 помечается как выполненное, добавляя новое задание в очередь заданий. Теперь в очереди 2 новых задания, которые обрабатываются в последовательности:

  3. Задание из шага 1 выполнено: p2 теперь разрешен. Это означает, что новое задание ставится в очередь, чтобы фактически вызвать соответствующий then callback (s)

  4. Выполнено задание из шага 2: выполняется обратный вызов с «tick: b»

Только позже будет выполнено задание, добавленное на шаге 3, который вызовет обратный вызов с "after: await".

Итак, в заключение. В EcmaScript resolve(p), где p - это доступное значение, включает асинхронное задание, которое само по себе запускает еще одно асинхронное задание для уведомления о выполнении.

Обратному вызову then, который отличает вторую версию кода, потребуется только одно асинхронное задание для вызова, и, таким образом, это происходит до вывода «tick: b».

0 голосов
/ 15 января 2019

В обоих ваших ответах цепочка обещаний1 и цепочка обещаний2 могут чередоваться по-разному.Но галочка: a, галочка: b, галочка: c будет выведена в таком порядке, галочка: a перед галочкой: b и галочка: b перед галочкой: c.после: await может быть выведено где угодно между.

Для того, что делает ваш код.

// Returns a resolved promise object
// Which is equivalent to const p = new Promise(resolve => resolve());
const p = Promise.resolve();

// For Reference Call This Promise Chain 1

new Promise((resolve) => {
    // Fulfills the promise with the promise object p 
    resolve(p);  // (1)
}).then(() => {
    console.log('after:await');
});

For Reference Promise Chain 2
p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'))
    .then(() => console.log('tick:c'));
const p = Promise.resolve();

new Promise((resolve) => {
    // Here you are calling then which if promise p has been fulfilled
    // will call the callback you passed as an argument, which then
    // will eventually cause the outer promise to enter a state of
    // fulfilled triggering a call to the next 'then' provided in the part of the chain. 
    p.then(()=>resolve());
}).then(() => {
    console.log('after:await');
});


p.then(() => console.log('tick:a'))
    .then(() => console.log('tick:b'))
    .then(() => console.log('tick:c'));

0 голосов
/ 15 января 2019

Метод Promise.resolve () возвращает объект Promise, который разрешен с данным значением. Если значение является обещанием, это обещание возвращается; если значение является доступным (то есть имеет "then" method), возвращаемое обещание будет «следовать» за этим доступным, принимая его возможное состояние; в противном случае возвращенное обещание будет выполнено со значением. Эта функция объединяет вложенные слои объектов, похожих на обещания (например, обещание, которое превращается в обещание, которое разрешается во что-то), в один слой.

Пожалуйста, обратитесь здесь для получения дополнительной информации о Promise.resolve ().

Разница в выводе обоих ваших кодов связана с тем, что обработчики then вызываются асинхронно.

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

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

0 голосов
/ 15 января 2019

Обещание находится в одном из следующих состояний:

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

Теперь в вашем конкретном случае вы используете «Promise.resolve ()», вероятно, для создания нового объекта обещания, но он создает уже разрешенное обещание без значения. Таким образом, ваш объект обещания "p" разрешается во время его создания, а остальная часть кода, в котором вы его обрабатываете, буквально не оказывает никакого влияния, кроме помещения "after: wait" в очередь обработчика. Пожалуйста, обратитесь к Циклу событий с нулевой задержкой . Вывод для обоих кодов различен в зависимости от того, когда «console.log» помещен в стек вызовов, а не от того, как вы его пишете.

Правильный способ сделать это может быть:

var promise1 = new Promise(function(resolve, reject) {
 setTimeout(function() {
  resolve('foo');
 }, 300);
});

promise1.then(function(value) {
 console.log(value);// expected output: "foo"
});

console.log(promise1);// expected output: [object Promise]
...