Promise & async/await


非同期操作を制御するために,コールバック関数を重ねるとコールバック地獄に陥る可能性がある.
コールバック地獄に陥り,コードのインデントレベルが深すぎると可読性が低下し,後でコードの修正も困難になる.
このcallback地獄を阻止するために利用できる方法は以下の通りである.
  • Promise
  • Generator
  • async/await
  • PromiseGeneratorはES 6に導入され、async/awaitはES 2017に導入された.
    その中でテクスチャが似ていると思われるPromiseasync/awaitが整理されている.
    簡単に言えば、Generatorは、yieldキーワードおよびnextメソッドによって関数内部コードを制御する順序で実行される.
    同期表示非同期タスク

    Promise

    Promiseオブジェクトは、非同期動作の結果を表す.Promiseには、次のいずれかの状態があります.
    待機
  • (保留):未履行または拒否の初期状態.
  • 完了
  • :演算が正常に完了しました.
  • 却下(却下):演算に失敗しました.
  • 待機状態から脱し、履行または拒否状態に入ると、解決したと言います.
    Promiseが待機状態で演算を実行し、実行または拒否になった場合(Promiseが処理された場合)、thenまたはcatchメソッドに追加されたタスクが実行される.
    すなわち、演算が完了するまで、Promiseに接続された他の動作には移行しない.
    したがって、非同期動作は、Promiseによって同期的に表すことができる.
  • MDNからのコード例
  • let myFirstPromise = new Promise((resolve, reject) => {
      // 우리가 수행한 비동기 작업이 성공한 경우 resolve(...)를 호출하고, 실패한 경우 reject(...)를 호출합니다.
      // 이 예제에서는 setTimeout()을 사용해 비동기 코드를 흉내냅니다.
      // 실제로는 여기서 XHR이나 HTML5 API를 사용할 것입니다.
      setTimeout( function() {
        resolve("성공!")  // 와! 문제 없음!
      }, 250)
    })
    
    myFirstPromise.then((successMessage) => {
      // successMessage는 위에서 resolve(...) 호출에 제공한 값입니다.
      // 문자열이어야 하는 법은 없지만, 위에서 문자열을 줬으니 아마 문자열일 것입니다.
      console.log("와! " + successMessage)
    });
    MDNには様々な方法が見られる.

    async/await

    Promiseを使用すると、thenのような方法がいくつかくっついて毒性が悪くなります.
    この場合はasync/awaitを使用し、毒性がより良く、使用がより便利である.async/awaitにおいてもPromiseが暗黙的に使用されているため、Promiseの代わりにasync/awaitが使用されるわけではない.Promise大臣よりPromiseを挙げるthenの方が適切です.

    どのように使いますか。

    asyncは非同期関数の前に付けられたキーワードであり、awaitはPromiseまたは待機している値の前に付けられたキーワードである.awaitは、async関数でのみ使用できます.
    コードを直接見て理解しましょう.
    function resolveAfter2Seconds() {
      return new Promise(resolve => {
        setTimeout(() => {
          resolve('resolved');
        }, 2000);
      });
    }
    
    async function asyncCall() {
      console.log('calling');
      const result = await resolveAfter2Seconds();
      console.log(result);
      // expected output: "resolved"
    }
    
    asyncCall();
    async functionからawaitのキーワードを持つ関数(Promise)に実行されるまで、awaitの後のコードには移動しません.
    このようにして、awaitは、Promisethenのように非同期ジョブごとに貼り付けることができる.async/awaitの目的は、GeneratorとPromiseを結合するように、複数のPromiseを同時に使用する動作である.

    async

    async関数は常にPromiseを返します.async関数の戻り値が明示的なPromiseでない場合、デフォルトはPromiseです.
    たとえば、foo関数がある場合は1を返します.
    async function foo() {
        return 1
    }
    上のfoo関数は下のコードと同じであると考えられる.
    戻り値にPromiseを入れなくても自動的にPromiseで包みます.
    function foo() {
        return Promise.resolve(1)
    }

    await

    awaitの後、Promiseが処理されるまで、Promiseを使用してasync関数を停止することができる.
    Promiseが処理されている場合は、停止した部分から実行します.
    [rv] = await expression;

  • expression
    Promiseまたは待機値(通常はPromiseを使用)

  • rv
    Promiseは満足のいく値を返します
    Promiseでない場合は、値自体を返します.
  • 返される値が重要でない場合は、await expression;、例えば[rv] = を省略して使用することができる.async関数の戻り値がPromiseでない場合、await以降の式がPromiseではないなどの解析Promiseに変換されます.

    async/await簡略化コードの使用


    実際に書いたコードを省いて持ってきました.
    コードでは、channelManager()はPromiseを返す関数です.
    channelManager.create()
    .then((category) => {
    	channelManager.create(category.id)
    	.then((channel) => {
    		channel.send(channelText);
    	});
    	channelManager.create(category.id)
    	.then((channel) => {
    		channel.send(channelText);
    	});
    });
    上記のコードにasync/awaitを適用してみます.
    const category = channelManager.create();
    
    const channel1 = await channelManager.create(category.id);
    await channel1.send(channelText);
            
    const channel2 = await channelManager.create(category.id);
    await channel2.send(channelText);
    上のコードと下のコードの動作は全く同じではありませんが、修正はそれほど悪くありません.
    一目でわかるように、async/awaitを使うともっと簡潔です.
    あとでPromiseを使うときもasync/await文法を使います.