web-dev-qa-db-ja.com

ES2017-非同期と収量

次のEcmaScriptに非同期関数とキーワードawaitを追加することについての現在の議論に戸惑っています。

asyncキーワードの前にfunctionキーワードを配置する必要がある理由がわかりません。

私の観点から見ると、awaitキーワードは、ジェネレーターまたはプロミスdoneの結果を待つために、関数のreturnで十分です。

awaitは、asyncマーカーを追加せずに、通常の関数およびジェネレーター関数内で簡単に使用できる必要があります。

また、awaitの結果として使用できる関数を作成する必要がある場合は、単にpromiseを使用します。

私が尋ねる理由は this 良い説明であり、次の例は次のようになります。

async function setupNewUser(name) {  
  var invitations,
      newUser = await createUser(name),
      friends = await getFacebookFriends(name);

  if (friends) {
    invitations = await inviteFacebookFriends(friends);
  }

  // some more logic
}

関数の実行がすべての待機が満たされるまでホール関数の終了を待機する場合は、通常の関数として実行することもできます。

function setupNewUser(name) {  
  var invitations,
      newUser = await createUser(name),
      friends = await getFacebookFriends(name);

  if (friends) {
    invitations = await inviteFacebookFriends(friends);
  }

  // return because createUser() and getFacebookFriends() and maybe inviteFacebookFriends() finished their awaited result.

}

私の意見では、関数の実行全体は、次のティック(実行待ち)が完了するまで保持されます。 Generator-Functionとの違いは、next()がオブジェクトの値とdoneフィールドをトリガーおよび変更していることです。代わりに、関数は、それが行われたときに結果を単純に返し、トリガーはwhileループのような関数の内部トリガーです。

32
Danny

Functionキーワードの前にasyncキーワードを置く必要がある理由がわかりません。

ジェネレーター関数の前に_*_記号があるのと同じ理由で、これらは関数を異常なものとしてマークします。それらはその点で非常に似ています-それらは、この関数の本体がそれ自体では完全には実行されないが、他のコードと任意にインターリーブできる視覚マーカーを追加します。

  • _*_はジェネレーター関数を示し、イテレーターのようにそれを消費することにより、外部から進められる(および停止される)ジェネレーターを常に返します。
  • asyncは非同期関数を示します。これは常に他のpromiseに依存し、その実行が他の非同期操作と並行している(そして外部からキャンセルされる可能性がある)promiseを常に返します。

キーワードは必ずしも必要ではなく、関数の種類は、それぞれのキーワード(yield(*)/await)が本文に表示されるかどうかで判断できますが、これにより、保守可能なコード:

  • 種類を判断するために全身をスキャンする必要があるため、わかりにくい
  • 構文エラーが発生することなく、キーワードを追加/削除することで関数を簡単に壊せるため、エラーが発生しやすくなります。

通常の関数。その実行は、すべての待機が満たされるまで穴本体の終了を待機します

verybad idea であるブロッキング機能が同時設定で必要なようです。

18
Bergi

関数をasyncとしてマークすることで、JSに常にPromiseを返すように指示しています。

常にreturn a Promiseになるため、独自のブロック内のpromiseでawaitすることもできます。それを1つの巨大なPromiseチェーンのように想像してみてください-関数の内部で発生することは、内部の.then()ブロックに効果的に結び付けられ、返されるのはチェーンの最後の.then()です。

たとえば、この関数...

_async function test() {
  return 'hello world';
}
_

... Promiseを返します。そのため、.then()などのように実行できます。

_test().then(message => {
  // message = 'hello world'
});
_

そう...

_async function test() {
  const user = await getUser();
  const report = await user.getReport();
  report.read = true
  return report;
}
_

にほぼ似ています...

_function test() {
  return getUser().then(function (user) {
    return user.getReport().then(function (report) {
      report.read = true;
      return report;
    });
  });
}
_

どちらの場合でも、test().then()に渡されるコールバックは、最初のパラメーターとしてreportを受け取ります。

ジェネレーター(つまり、関数_*_をマークし、yieldキーワードを使用する)は、まったく異なる概念です。彼らは約束を使用しません。これにより、コードのさまざまな部分の間を効果的にジャンプして、関数内から結果を生成し、そのポイントにジャンプして、次のyieldブロックに戻ることができます。

それらは多少似ています(つまり、何かがどこかで発生するまで実行を「停止」)しますが、_async/await_はPromise実行の内部順序を乱すであるため、その幻想を与えるだけです。 実際には待機していない-コールバックが発生したときにシャッフルしているだけです。

対照的に、ジェネレーターは異なる方法で実装されているため、ジェネレーターは状態を維持して反復することができます。繰り返しますが、Promiseとは関係ありません。

現在の執筆時点では、async/awaitのサポートが恐ろしいため、この線はさらにぼやけています。 Chakracoreはそれをネイティブでサポートしています V8は近日提供予定です 。それまでの間、Babelのようなトランスパイラーを使用すると、_async/await_を記述して、コードを generators に変換できます。したがって、ジェネレータとasync/awaitは同じであると結論するのは誤りです。それらはそうではありません... yieldがPromisesと一緒にどのように機能するかを粗末化して、同様の結果を得ることができるのは、たまたまです。

更新:2017年11月

Node LTSがネイティブ_async/await_をサポートするようになったため、Promiseをシミュレートするためにジェネレーターを使用する必要はありません。

14
Lee Benson

これらの回答はすべて、asyncキーワードが優れている理由について有効な引数を提供しますが、-hadが仕様に追加される本当の理由について実際に言及しているものはありません。

これは、これがES7より前の有効なJSであったためです

_function await(x) {
  return 'awaiting ' + x
}

function foo() {
  return(await(42))
}
_

あなたの論理によれば、foo()は_Promise{42}_または_"awaiting 42"_を返しますか? (Promiseを返すと、下位互換性が失われます)

したがって、答えは次のとおりです。awaitは通常の識別子であり、非同期関数内ではキーワードとしてのみ扱われるため、何らかの方法でマークを付ける必要があります。

おもしろい事実:元の仕様では、非同期構文に対してより軽量なfunction^ foo() {}が提案されていました。

7
phaux

前のasyncキーワードの理由は単純なので、戻り値がpromiseに変換されることがわかります。キーワードがない場合、インタプリタはこれをどのようにして知るでしょうか。これはC#で最初に導入されたものだと思います。EcmaScriptはTypeScriptからたくさんのものを取っています。 TypeScriptとC#は、Anders Hejlsbergによって考案され、類似しています。あなたが関数を持っているとしましょう(これは単に非同期の仕事をするためのものです)

_ function timeoutPromise() {  
     return (new Promise(function(resolve, reject) {
         var random = Math.random()*1000;
         setTimeout(
             function() {
                 resolve(random);
             }, random);
     }));
 }
_

この関数は、ランダムな時間待機し、Promise(jQueryを使用する場合、PromiseはDeferredに似ています)オブジェクトを返します。この関数を今日使用するには、次のように記述します

_function test(){
    timeoutPromise().then(function(waited){
        console.log('I waited' + waited);
    });
}
_

そして、これは結構です。ログメッセージを返してみましょう

_function test(){
    return timeoutPromise().then(function(waited){
        var message = 'I waited' + waited;
        console.log(message);
        return message; //this is where jQuery Deferred is different then a Promise and better in my opinion
    });
}
_

これは悪くありませんが、コードには2つのreturnステートメントと関数があります。

今非同期でこれはこのようになります

_  async function test(){
      var message = 'I waited' +  (await timeoutPromise());
      console.log(message);
      return message;
  }
_

コードは短く、インラインです。 .then()または。 done()コードがどれほど読みにくくなるかがわかります。

なぜasyncキーワードが関数の前にあるのか。これは、戻り値が返されたものではないことを示しています。理論的には、これを書くことができます(これはc#で実行できますが、jsが許可されているかどうかはわかりません)。

_ async function test(wait){
     if(wait == true){
         return await timeoutPromise();
     }
     return 5;                
 }
_

ご覧のとおり、数値を返しますが、実際の戻り値はPromiseです。使用する必要はありませんreturn new Promise(function(resolve, reject) { resolve(5);};数値を待つことができないため、Promise await test(false)のみがスローします例外とawait test(true)は、前に非同期を示さない場合は発生しません。

2
Filip Cordas