Почему асинхронная функция Javascript не возвращается немедленно? - PullRequest
9 голосов
/ 10 марта 2019

Я пытаюсь лучше понять асинхронные функции и обещания в JS.Для этого я написал пример программы, цель которой - вызвать функцию, которая выполняет занятую работу (целенаправленно не используя async setTimeout, поскольку я хочу имитировать длительный процесс), но немедленно возвращается.Тем не менее, я не могу понять, почему это не работает.

    test();
    
    async function intense(){
      var start = new Date().getTime();
      for (var i = 0; i < 1e6; i++) {
        if ((new Date().getTime() - start) > 2000){
          break;
        }
      }
      console.log("Done with async work");
    }
    async function test(){
        console.log("Print 1");
        intense(); // does some busy work for a few seconds
        console.log("Print 2"); // want this to print immediately after print 1
    }

Когда я запускаю его, я получаю:

Print 1
Done with async work
Print 2

И мне бы хотелось, чтобы оно было:

Print 1
Print 2
Done with async work

Iдумал, что он напечатает последнюю последовательность, потому что я объявил функцию intense() асинхронной, поэтому он немедленно вернет обещание и продолжит работу асинхронно.

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

async function intense(){
  return new Promise((resolve)=> {
      resolve();
      var start = new Date().getTime();
      for (var i = 0; i < 1e6; i++) {
        if ((new Date().getTime() - start) > 2000){
            break;
        }
  }
  console.log("Done with async work");
}, null)
}

Чего мне не хватает?

Ответы [ 3 ]

11 голосов
/ 10 марта 2019

Существует несколько причин, по которым вы видите:

  1. Функция async синхронна вплоть до ее первого await илиreturn, поэтому вся функция запускается до возврата в вашем случае.

  2. Ожидание при занятости не асинхронно.

  3. testнеобходимо использовать await, если он собирается дождаться завершения intense, прежде чем продолжить.

  4. Перемещение чего-либо в обещание не отнимает его в потоке.Единственный способ сделать это в большинстве сред JavaScript (включая браузеры) - использовать поток Worker ( MDN , Node.js docs - Node.js имеет Workerначиная с ~ v10.5, и хотя он по-прежнему помечен как "экспериментальный", основные части API должны быть достаточно стабильными, поскольку они взяты из стандартного API веб-рабочего).

Этоважно помнить, что обещания не делают ничего асинхронного¹, они обеспечивают наблюдение результата чего-то, что уже асинхронно.

Вот пример использования setTimeout для асинхронной части, которая поможет вам лучше понять это:

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

async function intense(value) {
    console.log("intense(" + value + ") - This is synchronous");
    await delay(100);
    console.log("intense(" + value + ") - This is asynchronous, because it's after `await`");
}

async function test(){
    console.log("Print 1");
    intense(1);       // <== WITHOUT await
    console.log("Print 2");
    await intense(2); // <== With await
    console.log("Print 3");
}

test();
.as-console-wrapper {
  max-height: 100% !important;
}

¹ Здесь есть одно маленькое предупреждение: обработчик, который вы передаете then, catch или finally, всегда будет вызыватьсяасинхронно, даже если обещание, на которое вы их зовете, уже выполнено.Это буквально only вещь, которая обещает фактически сделать асинхронной.

5 голосов
/ 10 марта 2019

поэтому он немедленно вернет обещание и продолжит работу асинхронно.

Нет, не будет.Обратный вызов, переданный конструктору Promise, вызывается немедленно.Что такое асинхронность, так это процесс вызова resolve или reject в дальнейшем и то, как цепочки .then когда-нибудь перезваниваются.

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

 console.log(1);
 const promise = new Promise((resolve, reject) => {
  console.log(2); // gets executed immeadiately 
 });

 promise.then(() => console.log(4)); // < Promise resolve asynchronously
console.log(3);

* Если вы планируете выполнять действительно «интенсивную» работу, может быть полезно сделать это в другом потоке(см. WebWorker в браузерах и child_process.spawn для NodeJS).

1 голос
/ 10 марта 2019

Это недоразумение, которое довольно легко сделать с помощью javascript.Ваша функция intense() блокирует поток.Помещение чего-либо в асинхронную функцию не меняет того факта, что у вас есть только один поток в javascript.Как только интерпретируемый запустит этот цикл for, он будет использовать один поток, чтобы запустить его, пока он не закончится.Ничего другого не произойдет до тех пор.

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

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

Подробнее см. Здесь: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

Если вы хотите асинхронно запускать такой долго выполняемый код, как этот, вам нужно будет запустить дочерний процесс: https://nodejs.org/api/child_process.html

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...