Обещания - Как заставить асинхронный код выполняться синхронно без async / await? - PullRequest
0 голосов
/ 07 января 2019
  var p1 = new Promise(function(resolve, reject) {  
    setTimeout(() => resolve("first"), 5000);
  });
  var p2 = new Promise(function(resolve, reject) {  
    setTimeout(() => resolve("second"), 2000);
  });
  var p3 = new Promise(function(resolve, reject) {  
    setTimeout(() => resolve("third"), 1000);
  });

  console.log("last to print");

p1.then(()=>p2).then(()=>p3).then(()=> console.log("last to be printed"))

Поскольку я читал об обещаниях, я знаю, что могу печатать обещания синхронно (в данном случае печатать: первый, второй, третий, последний для печати), когда я использую async / await. Теперь я также читал, что то же самое можно сделать с помощью .then, а async / await не является чем-то «особенным». Однако, когда я пытаюсь связать свои обещания, ничего не происходит, за исключением console.log "last to print". Любое понимание было бы здорово! Спасибо !!

Изменить на вопрос:

  var p1 = new Promise(function (resolve, reject) {
    setTimeout(() => console.log("first"), 5000);
    resolve("first resolved")
  });
  var p2 = new Promise(function (resolve, reject) {
    setTimeout(() => console.log("second"), 2000);
    resolve("second resolved")
  });
  var p3 = new Promise(function (resolve, reject) {
    setTimeout(() => console.log("third"), 0);
    resolve("third resolved")
  });

  console.log("starting");
  p1.then((val) => {
    console.log("(1)", val)
    return p2
  }).then((val) => {
    console.log("(2)", val)
    return p3
  }).then((val) => {
    console.log("(3)", val)
  })

Loggs:

starting
(1) first resolved
(2) second resolved
(3) third resolved
third
second
first

1: если функция executor, переданная новому Promise, выполняется немедленно, до того, как будет возвращено новое обещание, то почему здесь обещания разрешаются () синхронно) сначала и после того, как setTimeouts (асинхронно) выполняется?

  1. Возвращаемое значение в сравнении с обещанием разрешения:

    var sync = function () { вернуть новое обещание (функция (решить, отклонить) { setTimeout (() => { console.log ( "старт") разрешения ("привет") // - работает // вернуть "привет" // -> ничего не делает } 3000); }) } sync (). then ((val) => console.log ("val", val))

Ответы [ 2 ]

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

Функция исполнителя, которую вы передаете new Promise, выполняется немедленно , прежде чем будет возвращено новое обещание. Итак, когда вы делаете:

var p1 = new Promise(function(resolve, reject) {  
  setTimeout(() => resolve("first"), 5000);
});

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

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

Чтобы ваш код выполнял эти setTimeout вызовы только последовательно, когда истек предыдущий тайм-аут, вы не должны создавать новое обещание, пока не будет разрешено предыдущее обещание (используя более короткие тайм-ауты, чтобы избежать большого количества ожидания):

console.log("starting");
new Promise(function(resolve, reject) {  
  setTimeout(() => resolve("first"), 1000);
})
.then(result => {
    console.log("(1) got " + result);
    return new Promise(function(resolve, reject) {  
      setTimeout(() => resolve("second"), 500);
    });
})
.then(result => {
    console.log("(2) got " + result);
    return new Promise(function(resolve, reject) {  
      setTimeout(() => resolve("third"), 100);
    });
})
.then(result => {
    console.log("(3) got " + result);
    console.log("last to print");
});

Помните, что обещание ничего не делает и не меняет природу кода в исполнителе обещания. Все обещание дает средство наблюдения за результатом чего-либо (с действительно удобной комбинируемой семантикой).

Давайте выделим общие части этих трех обещаний в функцию:

function delay(ms, ...args) {
    return new Promise(resolve => {
        setTimeout(resolve, ms, ...args);
    });
}

Тогда код становится немного понятнее:

function delay(ms, ...args) {
    return new Promise(resolve => {
        setTimeout(resolve, ms, ...args);
    });
}

console.log("starting");
delay(1000, "first")
.then(result => {
    console.log("(1) got " + result);
    return delay(500, "second");
})
.then(result => {
    console.log("(2) got " + result);
    return delay(100, "third");
})
.then(result => {
    console.log("(3) got " + result);
    console.log("last to print");
});

Теперь давайте поместим это в функцию async и используем await:

function delay(ms, ...args) {
    return new Promise(resolve => {
        setTimeout(resolve, ms, ...args);
    });
}

(async() => {
    console.log("starting");
    console.log("(1) got " + await delay(1000, "first"));
    console.log("(2) got " + await delay(500, "second"));
    console.log("(3) got " + await delay(100, "third"));
    console.log("last to print");
})();

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


Повторное редактирование:

1: если функция-исполнитель, переданная новому Promise, выполняется немедленно, до того, как будет возвращено новое обещание, то почему здесь обещания разрешаются () синхронно) сначала и после выполнения setTimeouts (асинхронно)?

Этот вопрос состоит из двух частей:

A) "... почему здесь обещания разрешаются () синхронно) первыми ..."

B) "... почему здесь обещания разрешаются ... после выполнения setTimeouts (асинхронно)"

Ответ (A) таков: хотя вы разрешаете их синхронно, then всегда вызывает свой обратный вызов асинхронно. Это одна из гарантий, которые обещают предоставить. Вы решаете p1 (в этом редактировании), прежде чем функция исполнителя возвращается. Но то, как вы наблюдаете разрешения, гарантирует, что вы соблюдаете разрешения по порядку, потому что вы не начинаете наблюдать p2 до тех пор, пока p1 не разрешится, и тогда вы не начнете наблюдать p3 до разрешения p2.

Ответ на (B) таков: они этого не делают, вы решаете их синхронно, а затем наблюдаете эти разрешения асинхронно, и, поскольку они уже решены, это происходит очень быстро; позже запускаются обратные вызовы таймера. Давайте посмотрим, как вы создаете p1 в этом редактировании:

var p1 = new Promise(function (resolve, reject) {
  setTimeout(() => console.log("first"), 5000);
  resolve("first resolved")
});

Что там происходит:

  1. new Promise вызывается
  2. Вызывает функцию исполнителя
  3. Функция исполнителя вызывает setTimeout, чтобы запланировать обратный вызов
  4. Вы немедленно разрешаете обещание с "first resolved"
  5. new Promise возвращается, и разрешенное обещание присваивается p1
  6. Позже происходит тайм-аут, и вы выводите "first" на консоль

Тогда позже вы делаете:

p1.then((val) => {
  console.log("(1)", val)
  return p2
})
// ...

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

Поэтому, когда вы запускаете этот код, вы видите, что все три обещания разрешаются за до первого setTimeout обратного вызова & mdash; потому что обещания не ждут обратного вызова setTimeout.

Вам может быть интересно, почему вы видите свой последний then обратный вызов, запущенный до того, как вы увидите "third" в консоли, поскольку разрешения обещаний и console.log("third") происходят асинхронно, но очень скоро ( поскольку это setTimeout(..., 0) и все обещания предварительно разрешены): Ответ заключается в том, что разрешения обещаний равны микрозадачам и setTimeout, равным макрозадачам (или просто "задачам") , Все микрозадачи, выполняемые расписаниями задач, выполняются сразу же после завершения этой задачи (и все запланированные ими микрозадачи затем выполняются), прежде чем следующая задача будет взята из очереди задач. Итак, задача, выполняющая ваш скрипт, делает это:

  1. Запланирует задачу для обратного вызова setTimeout 1139 *
  2. Назначает микрозадачу для вызова p1 then callback
  3. Когда задача заканчивается, ее микрозадачи обрабатываются:
    1. Первый обработчик then запускается, планируя выполнение микрозадачи для запуска второго обработчика then
    2. Второй обработчик then запускает и планирует микро-задачу для вызова третьего обработчика then
    3. Etc. пока все then обработчики не запустятся
  4. Следующее задание берется из очереди заданий. Вероятно, это обратный вызов setTimeout для p3, поэтому он запускается и в консоли появляется "third"
  1. Возвращаемое значение против разрешения обещания:

Часть, которую вы задали в вопросе, не имеет смысла для меня, но ваш комментарий по этому поводу:

Я прочитал, что возвращение значения или разрешение обещания одно и то же ...

Вероятно, вы прочитали, что возвращение значения из then или catch - это то же самое, что возвращение разрешенного обещания из then или catch , Это потому, что then и catch создают и возвращают новые обещания при вызове, и если их обратные вызовы возвращают простое (не обещающее) значение, они разрешают созданное обещание с этим значением; если обратный вызов возвращает обещание, они разрешают или отклоняют созданное обещание в зависимости от того, разрешает или отклоняет это обещание.

Так, например:

.then(() => {
    return 42;
})

и

.then(() => {
    return new Promise(resolve => resolve(42));
})

имеют тот же конечный результат (но второй менее эффективен).

В пределах then или catch обратного вызова:

  1. Возврат без обещания разрешает обещание then / catch, созданное с этим значением
  2. Бросок ошибки (throw ...) отклоняет это обещание со значением, которое вы бросаете
  3. При возврате обещания then / catch обещание разрешается или отклоняется на основании обещания, которое обратный вызов возвращает
0 голосов
/ 07 января 2019

Нельзя заставить асинхронный код выполняться синхронно.

Даже async / await - это всего лишь синтаксис, который дает вам поток управления в синхронном стиле внутри обещания.

Однако, когда я пытаюсь связать свои обещания, ничего не происходит, за исключением console.log «последним будет напечатано». Любое понимание было бы здорово!

Другие функции не генерируют никакого вывода. Это не имеет ничего общего с их обещаниями.

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

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

function p1() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("first"), 5000);
  });
}

function p2() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("second"), 2000);
  });
}

function p3() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("third"), 1000);
  });
}

function log(value) {
  console.log("Previous promise resolved with " + value);
}

p1()
  .then(log)
  .then(p2)
  .then(log)
  .then(p3)
  .then(log)
  .then(() => console.log("last to be printed"));

Async / await, возможно, аккуратнее:

function p1() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("first"), 5000);
  });
}

function p2() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("second"), 2000);
  });
}

function p3() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => resolve("third"), 1000);
  });
}

function log(value) {
  console.log("Previous promise resolved with " + value);
}

(async function() {
  log(await p1());
  log(await p2());
  log(await p3());
  console.log("last to be printed");
}());
...