Разница между `return await prom` и` return prom`

108

Учитывая приведенные ниже примеры кода, есть ли разница в поведении, и если да, то каковы эти различия?

return await promise

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

return promise

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

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

Этот фрагмент представляет собой обычную функцию, возвращающую обещание для справки.

function delay(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}
PitaJ
источник
3
Да, я отредактировал свой вопрос, потому что вы неправильно поняли мой смысл, и он на самом деле не ответил на то, что мне было интересно.
PitaJ 01
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, с некоторыми интересными семантическими различиями.
Nosratio

Ответы:

156

В большинстве случаев заметной разницы между 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!'
  }
}

В первой версии функция async ожидает отклоненного обещания, прежде чем вернуть свой результат, что приводит к тому, что отклонение превращается в исключение и catch достигается условие; функция, таким образом, вернет обещание, разрешающееся в строку «Сохранено!».

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

Денис Вашингтон
источник
Может быть, также упомянуть, что трассировка стека будет другой (даже без попытки / улова)? Я думаю, что это проблема, с которой люди чаще всего сталкиваются в этом примере:]
Бенджамин Грюнбаум
В одном из сценариев я обнаружил, что использование return new Promise(function(resolve, reject) { })внутри for...ofцикла и последующий вызов resolve()внутри цикла после a pipe()не приостанавливают выполнение программы до тех пор, пока канал не будет завершен, как желательно, однако использование await new Promise(...)делает. последний даже действительный / правильный синтаксис? это «стенография» return await new Promise(...)? не могли бы вы помочь мне понять, почему последнее работает, а первое - нет? для контекста, сценарий в solution 02из этого ответа
user1063287
12

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

Оптимизация хвостовых вызовов или «правильные хвостовые вызовы» - это метод, который интерпретатор использует для оптимизации стека вызовов. В настоящее время не многие среды выполнения поддерживают его - хотя технически это часть стандарта 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), а второй возвращает Promise.
Амит
4
@Amit, обе функции возвращают обещание
PitaJ
Подтв. Вот почему я терпеть не могу async/await- мне гораздо труднее рассуждать. @PitaJ верен, обе функции возвращают обещание.
nrabinowitz 01
Что, если бы я заключил обе асинхронные функции в тело try-catch? В этом return promiseслучае ни один rejectionне будет пойман, верно, а в return await promiseслучае - верно?
PitaJ
Оба возвращают Promise, но первый «обещает» примитивное значение, а второй «обещает» Promise. Если вы awaitпосетите каждый из них на каком-либо сайте вызова, результат будет совсем другим.
Амит
1

Заметная разница: отклонение обещаний обрабатывается в разных местах.

  • 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сначала будет ждать обещания поселиться на месте. Следовательно, значение или исключение сначала обрабатывается локально. => Локальный блок перехвата будет выполнен, если 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будет урегулировано.
  • ЕСЛИ delayPromiseвыполняется с PromiseValue = 1:
const result = 1; 
  • ELSE 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
Регтайм
источник
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» - это просто асинхронная функция, чем у других функций, если она удалит возвращаемый результат, напечатает «больше кода ...»

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

ниже "console.log (" finished 1 "удалите" return ", вы увидите поведение.

Карлос Террасас
источник
1
Хотя этот код может решить вопрос, в том числе объяснение того, как и почему это решает проблему, действительно поможет улучшить качество вашего сообщения и, вероятно, приведет к большему количеству голосов за. Помните, что вы отвечаете на вопрос для будущих читателей, а не только для человека, который задает его сейчас. Пожалуйста , измените свой ответ , чтобы добавить объяснения и дать указание о том , что применять ограничения и допущения.
Брайан,
0

Вот пример машинописного текста, который вы можете запустить и убедить себя, что вам нужно "return await"

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")
});

Дэвид Дехган
источник