JavaScript, возвращающий новое обещание без оператора resolve(), не ожидает, как ожидалось

У меня есть очень простой фрагмент кода, подобный этому

async function neverResolve() {
  return new Promise(() => {
    console.info("This promise will never resolve");
  });
}

(async () => {
  try {
    console.info("START");
    // neverResolve().then().catch(); // uncommenting this line works as expected
    await neverResolve();
    await new Promise((resolve) => setTimeout(() => resolve(), 5000));
    console.info("END");
  } catch (error) {
    console.info("ERR: ", error);
  }
})();

Почему вышеуказанная функция не ждет 5 секунд и не печатает END. Он автоматически завершается после печати

START
This promise will never resolve

Но если мы выполним ту же функцию, но с конструкцией .then(), я получу ожидаемый результат.

async function neverResolve() {
  return new Promise(() => {
    console.info("This promise will never resolve");
  });
}

(async () => {
  try {
    console.info("START");
    neverResolve().then().catch(); 
    await new Promise((resolve) => setTimeout(() => resolve(), 5000));
    console.info("END");
  } catch (error) {
    console.info("ERR: ", error);
  }
})();

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

Guillaume Brunerie 06.02.2023 21:31
neverResolve().then().catch(); не ждет. Это огонь и забыть. Поэтому код будет продолжаться после него. Первый никогда не разрешается, и в конечном итоге процесс уничтожается. Нет реальной причины хранить его вечно.
VLAZ 06.02.2023 21:31

@GuillaumeBrunerie не имеет большого значения. С async function обещание от neverResolves() примет состояние возвращенного обещания. Итак, это просто то же неразрешимое обещание в конце.

VLAZ 06.02.2023 21:32

Я добавляю несколько общих советов: async/await — это просто синтаксический сахар. Когда вы испытываете другое поведение в коде, который является просто синтаксическим сахаром, вы можете просто вставить его в BabelJS и посмотреть, как он преобразуется.

Christian Vincenzo Traina 06.02.2023 21:37
await new Promise(() => {}); это зло
Shankar Regmi 06.02.2023 22:24
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
5
85
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

В первом примере await neverResolve(); ждет вечно и никогда не разрешается, как указано. Javascript может отключаться и делать другие вещи в других задачах, ожидая этого.

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

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

async function slowFetch() {
  return new Promise((resolve) => {
    // a fetch statement that takes forever
    fetch().then(data => resolve(data));
  });
}

(async () => {
  try {
    console.info("START");

    // start the slow fetch right away
    const dataPromise = slowFetch();

    // do some other tasks that don't take as long
    await new Promise((resolve) => setTimeout(() => resolve(), 5000));

    // wait for the data to arrive
    const data = await dataPromise;

    // do something with the data like fill in the page.

    console.info("END");
  } catch (error) {
    console.info("ERR: ", error);
  }
})();

ОП спрашивает, почему возврат промиса, который никогда не вызывает resolve(), не выполняет блок кода после его ожидания, но в вашем примере вы выполняете resolve() inside a slowFetch()``

Shankar Regmi 06.02.2023 21:39
new Promise((resolve) => { fetch().then(data => resolve(data));}); — исключительно плохой пример, так как он просто поощряет бессмысленный и опасный явный антипаттерн построения промисов. Тот, что в этом коде, никогда не обрабатывает отклоненное обещание fetch(), поэтому существует реальный риск стать неразрешимым.
VLAZ 06.02.2023 21:40

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

NetByMatt 06.02.2023 21:42

Делая neverResolve().then().catch(); то, что вы на самом деле сделали;

async function neverResolve() {
  return new Promise(() => {
    console.info("This promise will never resolve");
  });
}

(async () => {
  try {
    console.info("START");
    (async function(){
       try {
         await neverResolve()
       }
       catch{
       }
     })(); 
    await new Promise((resolve) => setTimeout(() => resolve(), 5000));
    console.info("END");
  } catch (error) {
    console.info("ERR: ", error);
  }
})();

Внутренний асинхронный IIFE работает так же, как neverResolve().then().catch();. Вы не должны смешивать обещания с async await абстракцией.

Почему вышеуказанная функция не ждет 5 секунд и печатает END. Он автоматически завершается после печати

Потому что ваш код никогда не проходит мимо этого:

await neverResolve();

Поскольку neverResolve() возвращает промис, который никогда не разрешается и не отклоняется, эта функция навсегда приостанавливается на этой строке, а строки кода после этого оператора в функции никогда не выполняются.

await означает, что выполнение функции должно быть приостановлено на неопределенный срок, пока обещание, которое вы ожидаете, не будет разрешено или отклонено.


Но если мы выполним ту же функцию, но с конструкцией .then(), я получу ожидаемый результат.

Когда вы меняете await на это:

neverResolve().then().catch(); 

Выполнение функции вообще НЕ приостановлено. Выполняется neverResolve(). Это возвращает обещание, которое затем вызывает .then(). Этот вызов .then() просто регистрирует обратный вызов с обещанием (который будет вызываться позже, когда обещание разрешается). Это возвращает другое обещание, которое затем вызывает .catch(), на котором просто регистрируется обратный вызов с обещанием (который будет вызываться позже, если/когда обещание отклоняется).

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


Резюме

await приостанавливает выполнение функции до тех пор, пока обещание, которое вы ожидаете, не будет разрешено или отклонено.

.then() и .catch() просто зарегистрируйте обратные вызовы для некоторого будущего изменения состояния обещания. Они не блокируют. Они не приостанавливают выполнение функции. Они регистрируют обратный вызов и тут же возвращаются.

neverResolve не разрешает и не отклоняет, поэтому программа зависает на неопределенное время на await. Рассмотрите возможность абстрагирования функциональности тайм-аута в своей собственной универсальной функции, timeout -

const sleep = ms =>
  new Promise(r => setTimeout(r, ms));

const timeout = (p, ms) =>
  Promise.race([
    p,
    sleep(ms).then(() => { throw Error("timeout") })
  ]);
  
const neverResolve = () => new Promise(() => {});

(async function() {
  try {
    console.info("connecting...");
    await timeout(neverResolve(), 2000);
  }
  catch (err) {
    console.error(err);
  }
})();
Ответ принят как подходящий

Вот что происходит в вашем случае:

  1. Ваша программа начинает выполнение с анонимной асинхронной функции IIFE, поскольку это асинхронная функция, она немедленно возвращает обещание в глобальную область. Таким образом, выполнение анонимного IIFE откладывается. Вы можете легко проверить это, добавив console.info("OK"); в конце вызова IIFE, который выводится на консоль.

  2. Node ведет учет таких вещей, как таймеры и сетевые запросы. Когда вы делаете сетевой или другой асинхронный запрос, устанавливаете таймер и т. д. Node добавляет к этому счетчику ссылок. Когда время/запрос разрешено, Node вычитает из подсчета. Реф. ссылка на видео Итак, что происходит внутри вашего IIFE:

    • console.info("START"); <--- выводится на консоль
    • await neverResolve(); Здесь все становится интереснее, этот вызов await откладывает выполнение и блокируется до тех пор, пока не будет выполнен обратный вызов, либо resolve, либо reject.

    Но в этом случае обратные вызовы не зарегистрированы, и nodejs будет думать, что закончил обработку всего запроса, и завершит процесс.

Это имеет смысл после просмотра ссылки на видео, которую вы разместили, и объяснение было по делу.

Anonymous Zombie 07.02.2023 13:00

Другие вопросы по теме