「返品待ち」と「返品保証」の違い


105

以下のコードサンプルを考えると、動作に違いはありますか。ある場合、それらの違いは何ですか?

return await promise

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

return promise

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

私が理解しているように、1つ目はasync関数内でエラー処理が行われ、async関数のPromiseからエラーが発生します。ただし、2番目は1ティック少ない必要があります。これは正しいです?

このスニペットは、参考のためにPromiseを返すための一般的な関数にすぎません。

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

3
ええ、私が質問を編集したのは、あなたが私の意味を誤解していて、私が疑問に思っていることを実際には答えなかったからです。
PitaJ

1
@PitaJ:async2つ目の(return promise)サンプルからを削除するつもりだったと思います。
Stephen Cleary

1
@PitaJ:その場合、2番目の例は、promiseで解決されるpromiseを返します。むしろ奇妙です。
Stephen Cleary

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 2018

回答:


151

時間のほとんどは、間に観察できる差がないreturnreturn await。の両方のバージョンのdelay1Second監視可能な動作はまったく同じです(ただし、実装によっては、return await中間Promiseオブジェクトが作成される可能性があるため、バージョンのメモリ使用量が若干増える場合があります)。

ただし、@ PitaJが指摘したように、returnまたはブロックreturn awaittry- またはにネストされている場合、違いがある1つのケースがあり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関数は結果を返す前に拒否されたpromiseを待機します。これにより、拒否が例外に変わり、catch句に到達します。したがって、関数は文字列「Saved!」を解決するpromiseを返します。

関数の第二版は、しかし、直接拒否された約束を返さない非同期機能の中にそれを待たずにいることを意味、catchケースがされていないと呼ばれ、呼び出し側が代わりに拒否を取得します。


おそらく、スタックトレースが異なる(トライ/キャッチがない場合でも)ことにも言及しますか?この例では、人々が最もよく
遭遇

私は1つのシナリオreturn new Promise(function(resolve, reject) { })で、for...ofループ内で使用し、パイプが完了するまでプログラムの実行を一時停止しなかったresolve()後、ループ内で呼び出しますが、pipe()必要に応じて使用await new Promise(...)します。後者は有効/正しい構文ですか?それは「省略形」return await new Promise(...)ですか?後者が機能し、前者が機能しない理由を理解していただけますか?コンテキストについては、シナリオはこの回答の中にsolution 02あります
user1063287 '15

10

他の回答が述べたように、最初に結果を待ってからもう一度別のプロミスでラップする必要がないという理由だけで、プロミスを直接返すことによってバブルを上げた場合、パフォーマンスがわずかに向上する可能性があります。ただし、テールコールの最適化についてはまだ誰も話していません。

末尾呼び出しの最適化、または「適切な末尾呼び出し」は、インタープリターが呼び出しスタックを最適化するために使用する手法です。現在のところ、技術的にはES6標準の一部ですが、まだ多くのランタイムでサポートされていませんが、将来的にサポートが追加される可能性があるため、現時点で適切なコードを記述することで準備できます。

簡単に言えば、TCO(またはPTC)は、別の関数から直接返される関数の新しいフレームを開かないことで、コールスタックを最適化します。代わりに、同じフレームを再利用します。

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

はからdelay()直接返されるためdelay1Second()、PTCをサポートするランタイムは最初にdelay1Second()(外部関数)のフレームを開きますが、(内部関数)の別のフレームを開く代わりにdelay()、外部関数用に開いたのと同じフレームを再利用します。非常に大きな再帰関数(など)でスタックオーバーフロー(hehe)を防ぐことができるため、これによりスタックが最適化されますfibonacci(5e+25)。基本的にはループになり、はるかに高速になります。

PTCは、内部関数が直接返される場合にのみ有効になります。それはあなたが持っていた場合には、例えば、返される前に、関数の結果が変更されるときに使用されていないreturn (delay(1000) || null)、またはreturn await delay(1000)

しかし、先ほど言ったように、ほとんどのランタイムとブラウザーはまだPTCをサポートしていないため、おそらく大きな違いはありませんが、コードの将来性を損なうことはありません。

この質問の詳細を読む:Node.js:非同期関数の末尾呼び出しの最適化はありますか?


2

babel実際にはトランスパイラーが(おそらく)実際にどのようにレンダリングされるかに依存するため、これは答えるのが難しい質問ですasync/await。関係なく明確なこと:

  • 最初の実装ではチェーンの数が1つ少なくなる場合がありますが、両方の実装は同じように動作するはずPromiseです。

  • 特に、不要なを削除した場合await、2番目のバージョンではトランスパイラーからの追加コードは不要になりますが、最初のバージョンでは不要です。

したがって、コードのパフォーマンスとデバッグの観点から、2番目のバージョンの方が望ましいですが、非常にわずかですが、最初のバージョンは、読みやすさの利点があり、promiseを返すことを明確に示しています。


関数が同じように動作するのはなぜですか?1つ目は解決された値(undefined)を返し、2つ目はを返しますPromise
2016

4
@Amit両方の関数がPromiseを返す
PitaJ

ああ。これが私が我慢できない理由ですasync/await-私はそれを推論するのがはるかに難しいと思います。@PitaJは正しいです。どちらの関数もPromiseを返します。
nrabinowitz

両方の非同期関数の本体をtry-catch?で囲むとどうなりますか?そのreturn promise場合、rejection捕まえられないものはありますが、その場合、捕まえられreturn await promiseますよね?
PitaJ

どちらもPromiseを返しますが、1つ目はプリミティブ値を「約束」し、2つ目はPromiseを「約束」します。あなたの場合はawait、いくつかの呼び出しサイトでこれらの各、結果は非常に異なるものになります。
2016

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関数の内部には別の非同期関数があり、これはpromiseを返すか、または "promise"を返します。これは、x関数の内部にとどまり、メインコードを忘れます。つまり、 "console.log(" more code .. ")、一方、「置く場合」await"完了してすべての関数が完了するのを待ち、最終的にメインコードの通常のシーケンスに従います。

「console.log(「終了1」「削除」「戻り」」の下に、動作が表示されます。


1
このコードは問題を解決する可能性がありますが、これが問題を解決する方法と理由の説明含めると、投稿の品質が向上し、投票数が増える可能性があります。あなたが今尋ねている人だけでなく、将来の読者のための質問に答えていることを忘れないでください。回答を編集して説明を追加し、適用される制限と前提を示してください。
ブライアン

0

これは、実行して「戻り待ち」が必要であることを確信できるtypescriptの例です。

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最初に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)はすぐにpromiseを返します[[PromiseStatus]]: 'pending。それを呼びましょうdelayPromise
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. 非同期関数は、戻り値をPromise.resolve()Source)でラップします。delay1Secondは非同期関数なので、次のようになります。
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)delayPromise入力はすでにpromiseであるため、何もせずに戻ります(MDN Promise.resolveを参照)。
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. awaitdelayPromiseが解決するまで待機します。
  • IF 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)はすぐにpromiseを返します[[PromiseStatus]]: 'pending。それを呼びましょうdelayPromise
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. ローカル待機delayPromiseは、解決するまで待機します。
  • ケース1delayPromisePromiseValue = 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; 
  • ケース2delayPromise拒否されました:
// 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への変更resolvedrejected
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.