Різниця між "повернення чекає обіцянки" та "повернення обіцянки"


106

Враховуючи наведені нижче зразки коду, чи є якась різниця у поведінці, і, якщо так, то які ці відмінності?

return await promise

async function delay1Second() {
  return (await delay(1000));
}

return promise

async function delay1Second() {
  return delay(1000);
}

Наскільки я розумію, перший мав би обробляти помилки в рамках функції асинхронізації, і помилки виходили з обіцянки функції асинхронізації. Однак для другого потрібно було б на одного кліща менше. Це правильно?

Цей фрагмент - це просто поширена функція повернення Promise для посилання.

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

3
Так, я відредагував своє запитання, бо ви неправильно зрозуміли моє значення, і воно насправді не відповідало на те, що мені цікаво.
PitaJ

1
@PitaJ: Я вважаю, що ви мали намір видалити asyncз вашого другого ( return promise) зразка.
Стівен Клірі

1
@PitaJ: У такому випадку ваш другий приклад поверне обіцянку, яка вирішується за допомогою обіцянки. Досить дивно.
Стівен Клірі

5
jakearchibald.com/2017/await-vs-return-vs-return-await - це гарна стаття, яка узагальнює відмінності
sanchit

2
@StephenCleary, я натрапив на це і спочатку подумав точно те саме: обіцянка, яка вирішується за допомогою обіцянки, тут не має сенсу. Але, як виявляється, promise.then(() => nestedPromise)буде сплющуватися і "слідувати" за nestedPromise. Цікаво, чим це відрізняється від вкладених завдань у C #, де нам це доведеться Unwrap. У додатковій примітці виявляється, що await somePromise дзвінки Promise.resolve(somePromise).then, а не просто somePromise.then, мають деякі цікаві семантичні відмінності.
noseratio

Відповіді:


152

Здебільшого між returnі не спостерігається різниці return await. Обидві версії delay1Secondмають однаково спостережувану поведінку (але залежно від реалізації return awaitверсія може використовувати трохи більше пам'яті, оскільки Promiseможе бути створений проміжний об'єкт).

Однак, як зазначив @PitaJ, є один випадок, коли є різниця: якщо returnабо return awaitвкладено в блок try- catch. Розглянемо цей приклад

async function rejectionWithReturnAwait () {
  try {
    return await Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

async function rejectionWithReturn () {
  try {
    return Promise.reject(new Error())
  } catch (e) {
    return 'Saved!'
  }
}

У першій версії функція асинхронізації очікує на відхилену обіцянку перед поверненням її результату, що призводить до того, що відхилення перетворюється на виняток і catchдосягається положення; функція, таким чином, поверне розв'язування обіцянки у рядок "Збережено!"

Однак друга версія функції повертає відхилене обіцянку безпосередньо, не чекаючи його в межах функції асинхронізації , що означає, що catchсправа не викликається, а натомість виклик отримує відхилення.


Може також згадати, що трасування стека було б іншим (навіть без спроби / лову)? Я думаю, що це проблема, з якою люди найчастіше стикаються в цьому прикладі:]
Бенджамін

Я виявив за одним сценарієм, що використання return new Promise(function(resolve, reject) { })всередині for...ofциклу, а потім виклик resolve()всередині циклу після a pipe()не призупиняє виконання програми, поки труба не завершиться, як бажано, однак використання await new Promise(...)робить. чи є останній навіть дійсним / правильним синтаксисом? це "скорочення" для return await new Promise(...)? не могли б ви допомогти мені зрозуміти, чому остання працює, а перша - ні? для контексту, сценарій в solution 02з цієї відповіді
user1063287

10

Як зазначалося в інших відповідях, існує ймовірність невеликої переваги в продуктивності, коли обіцянка з’являється, повертаючи її безпосередньо - просто тому, що вам не потрібно спочатку чекати результату, а потім знову обертати його іншою обіцянкою. Однак про оптимізацію хвостових викликів ще ніхто не говорив .

Оптимізація зворотного виклику , або “правильні зворотні виклики” , - це техніка, яку інтерпретатор використовує для оптимізації стека викликів. В даний час ще не багато середовищ виконання підтримують його - хоча технічно він є частиною стандарту ES6, - але можливо, підтримка може бути додана в майбутньому, тому ви можете підготуватися до цього, написавши хороший код в даний час.

У двох словах, TCO (або PTC) оптимізує стек викликів, не відкриваючи новий кадр для функції, яка безпосередньо повертається іншою функцією. Натомість він повторно використовує той самий кадр.

async function delay1Second() {
  return delay(1000);
}

Оскільки delay()безпосередньо повертається delay1Second(), середовища виконання, які підтримують PTC, спочатку відкриють кадр для delay1Second()(зовнішньої функції), але потім замість того, щоб відкрити інший кадр для delay()(внутрішньої функції), він просто повторно використає той самий кадр, який був відкритий для зовнішньої функції. Це оптимізує стек , тому що це може запобігти переповнення стека (хе - хе) з дуже великими функціями рекурсивних, наприклад, fibonacci(5e+25). По суті, це стає циклом, що набагато швидше.

PTC вмикається лише тоді, коли внутрішня функція повертається безпосередньо . Він не використовується, коли результат функції змінюється перед поверненням, наприклад, якщо у вас було return (delay(1000) || null), або return await delay(1000).

Але, як я вже говорив, більшість середовищ виконання та браузерів ще не підтримують PTC, тому це, мабуть, не має великої різниці зараз, але це не може зашкодити забезпеченню вашого коду в майбутньому.

Детальніше читайте в цьому питанні: Node.js: Чи існують оптимізації для викликів хвоста в асинхронних функціях?


2

На це питання важко відповісти, оскільки на практиці це залежить від того, як babelнасправді відображається ваш транспілер async/await. Речі, які зрозумілі незалежно від:

  • Обидві реалізації повинні поводитися однаково, хоча перша реалізація може мати Promiseна ланцюжку менше .

  • Особливо, якщо ви викинете непотрібне await, друга версія не потребуватиме додаткового коду від транпілера, тоді як перша вимагає.

Отже, з точки зору продуктивності коду та налагодження, друга версія є кращою, хоча і дуже незначною, хоча перша версія має невелику перевагу розбірливості, оскільки вона чітко вказує на те, що вона повертає обіцянку.


Чому функції поводяться однаково? Перший повертає вирішене значення ( undefined), а другий повертає a Promise.
Amit

4
@Amit обидві функції повертають обіцянку
PitaJ

Ак. Ось чому я не терплю async/await- мені набагато важче міркувати про це. @PitaJ правильний, обидві функції повертають Promise.
nrabinowitz

Що, якби я оточив тіло обох асинхронних функцій символом a try-catch? У цьому return promiseвипадку ніхто rejectionне буде зловлений, правильно, тоді як, у return await promiseвипадку, це буде так, так?
PitaJ

Обидва повертають обіцянку, але перша «обіцяє» примітивне значення, а друга «обіцяє» обіцянку. Якщо ви awaitкожен із них на якомусь сайті дзвінка, результат буде дуже різним.
Amit

0

тут я залишаю деякий практичний код, щоб ви могли зрозуміти його різницю

 let x = async function () {
  return new Promise((res, rej) => {
    setTimeout(async function () {
      console.log("finished 1");
      return await new Promise((resolve, reject) => { // delete the return and you will see the difference
        setTimeout(function () {
          resolve("woo2");
          console.log("finished 2");
        }, 5000);
      });
      res("woo1");
    }, 3000);
  });
};

(async function () {
  var counter = 0;
  const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
    if (counter == 7) {
      clearInterval(a);
    }

    console.log(counter);
    counter = counter + 1;
  }, 1000);
  console.time("time1");
  console.log("hello i starting first of all");
  await x();
  console.log("more code...");
  console.timeEnd("time1");
})();

функція "x" просто є функцією асинхронізації, ніж у інших fucn, якщо видалить повернення, надрукуйте "більше коду ..."

змінна x - це просто асинхронна функція, яка, у свою чергу, має іншу асинхронну функцію; в основному коду ми викликаємо очікування для виклику функції змінної x, коли вона завершується, вона слідує послідовності коду, що було б нормально для "async / await", але всередині функції x є інша асинхронна функція, і вона повертає обіцянку або повертає "обіцянку", вона залишиться всередині функції x, забувши основний код, тобто вона не надрукує "console.log (" більше коду .. "), з іншого боку, якщо ми поставимо" await ", він буде чекати кожної функції, яка завершується, і нарешті дотримується нормальної послідовності основного коду.

під "console.log (" закінчено 1 "видаліть" return ", ви побачите поведінку.


1
Хоча цей код може вирішити питання, включаючи пояснення того, як і чому це вирішує проблему, справді допомогло б поліпшити якість вашої публікації, і, можливо, призведе до збільшення кількості голосів. Пам’ятайте, що ви відповідаєте на запитання читачам у майбутньому, а не лише тому, хто задає зараз. Будь ласка, відредагуйте свою відповідь, щоб додати пояснення та вказати, які обмеження та припущення застосовуються.
Брайан,

0

Ось приклад машинопису, який ви можете запустити і переконати себе, що вам потрібно "повернення чекає"

async function  test() {
    try {
        return await throwErr();  // this is correct
        // return  throwErr();  // this will prevent inner catch to ever to be reached
    }
    catch (err) {
        console.log("inner catch is reached")
        return
    }
}

const throwErr = async  () => {
    throw("Fake error")
}


void test().then(() => {
    console.log("done")
}).catch(e => {
    console.log("outer catch is reached")
});


0

Помітна різниця: відхилення обіцянок обробляється в різних місцях

  • return somePromiseпередасть somePromise на сайт виклику, а await somePromise для поселення на сайті виклику (якщо такий є). Отже, якщо somePromise відхилено, його не буде обробляти локальний блок catch, а блок catch сайту виклику.

async function foo () {
  try {
    return Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'OUT'

  • return await somePromiseспочатку буде чекати деякої обіцянки для поселення на місцевому рівні. Тому значення або виняток спочатку оброблятиметься локально. => Локальний блок catch буде виконаний, якщо його somePromiseбуде відхилено.

async function foo () {
  try {
    return await Promise.reject();
  } catch (e) {
    console.log('IN');
  }
}

(async function main () {
  try {
    let a = await foo();
  } catch (e) {
    console.log('OUT');
  }
})();
// 'IN'

Причина: return await Promiseчекає як місцево, так і зовні, return Promiseчекає лише зовні

Детальні кроки:

повернути обіцянку

async function delay1Second() {
  return delay(1000);
}
  1. дзвінок delay1Second();
const result = await delay1Second();
  1. Усередині delay1Second()функція delay(1000)негайно повертає обіцянку за допомогою [[PromiseStatus]]: 'pending. Назвемо це delayPromise.
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Асинхронні функції обернуть своє повернене значення всередині Promise.resolve()( Джерело ). Оскільки delay1Secondце функція асинхронізації, ми маємо:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)повертається, delayPromiseне роблячи нічого, оскільки вхідні дані вже є обіцянкою (див. MDN Promise.resolve ):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitчекає, поки не delayPromiseбуде врегульовано.
  • IF delayPromiseвиконується з PromiseValue = 1:
const result = 1; 
  • Інакше delayPromiseвідхиляється:
// jump to catch block if there is any

повернення чекає обіцянки

async function delay1Second() {
  return await delay(1000);
}
  1. дзвінок delay1Second();
const result = await delay1Second();
  1. Усередині delay1Second()функція delay(1000)негайно повертає обіцянку за допомогою [[PromiseStatus]]: 'pending. Назвемо це delayPromise.
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. Місцеві очікують чекатимуть, поки delayPromiseвладнаються.
  • Випадок 1 : delayPromiseвиконується з PromiseValue = 1:
async function delay1Second() {
  return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise; 
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1; 
  • Випадок 2 : delayPromiseвідхилено:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;

Глосарій:

  • Заселення: Promise.[[PromiseStatus]]зміни від pendingдо resolvedабоrejected
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.