浅談async/await


背景
ES 7が提案したasync/awaitはJavaScriptが非同期問題を解決するために提案した解決策であり、これ以上のコールバックはなく、多くの人が非同期の究極の解決策と呼んでいる.async関数はGenerator関数のシンタックス糖である.キーワードasyncで表し、関数内部でawaitで非同期を表します.JavaScriptの発展もコールバック、Promise、async/awaitの3つの段階を経験し、この文章は私自身のasync/awaitに対する理解を書いた.悪いところはみんなに指摘してもらい、共に進歩してください.
コンセプト
asyncは「非同期」の略であり、asyncはfunctionが非同期であることを明らかにし、awaitは非同期メソッドの実行が完了するのを待っており、awaitはasync関数にしか現れない.async関数は完全に複数の非同期操作と見なすことができ,パッケージされたPromiseオブジェクトであり,awaitコマンドは内部thenコマンドの構文糖である.
  • asyncの役割async関数はPromiseオブジェクトを返す責任を負います.async関数で1つの変数をreturnすると、asyncはこの変数をPromise.resolve()を介してPromiseオブジェクトにカプセル化します.async関数に値が返されない場合は、Promise.resolve(undefined)が返されます.
  • awaitは何を待っていますか
  • 一般的にawaitでasync関数の完了を待つが、awaitはPromiseオブジェクトまたは他の値を計算する式を待つので、awaitの後で実際には一般的な関数呼び出しまたは直接量を受信することができる.
    awaitがpromiseオブジェクトでない場合、式の演算結果に従って待つものです.promiseオブジェクトの場合、awaitは後ろのコードをブロックし、promiseオブジェクトresolveを待って、resolveの値をawait式の演算結果として得ます.awaitはブロックされていますが、awaitはasyncでasyncはブロックされず、内部のすべてのブロックはpromiseオブジェクトにカプセル化されて非同期で実行されます.
    メリット
    Generatorとは異なり、async関数の改善点は次の4つです.
  • にはアクチュエータが内蔵されています.Generator関数の実行はアクチュエータに依存しなければならないが,async関数はアクチュエータを備えており,呼び出し方式は通常の関数の呼び出しと同じである.
  • より良い意味.asyncとawaitは*とyieldよりも意味化されています.
  • より広い適用性.Coモジュールはyieldコマンドの後ろにThunk関数またはPromiseオブジェクトしかないことを約束している.一方、async関数のawaitコマンドの後ろには、Promiseまたは元のタイプの値(Number,string,boolean)がありますが、この場合は同期操作と同等です.
  • 戻り値はPromise、async関数戻り値はPromiseオブジェクトであり、Generator関数が返すIteratorオブジェクトよりも便利で、then()メソッドを直接使用して呼び出すことができます.

  • 特長
  • はpromiseの上に構築されているので、コールバック関数と組み合わせて使用することはできませんが、非同期関数を宣言し、Promiseを暗黙的に返します.したがって、Promise.resolveを使用して変換することなく、直接return変数を使用できます.
  • はpromiseと同様に非ブロックであるが、thenとそのコールバック関数を書く必要はなく、コード行数を減らし、コードネストを回避し、すべての非同期呼び出しを同じコードブロックに書くことができ、余分な中間変数を定義する必要はない.
  • の最大の価値は、非同期コードを同期コードに形式的に近づけることができることである.
  • は常にawaitとともに使用され、awaitはasync関数体内の
  • のみである.
  • awaitは、式を構成するための演算子であり、後続のコードがブロックされます.Promiseオブジェクトが待機している場合はresolve値が得られ、そうでない場合は式の演算結果が得られます.

  • async/await使用規則
    非同期を処理する場合,Promiseのthen法はコールバック関数よりも簡潔で明瞭に見えるが,互いに依存する複数の要求を処理する際には多少煩雑である.この時、asyncとawaitを使うともっと優雅になります.
  • ルール1:asyncを前に追加した関数は、実行後にPromiseオブジェクトを自動的に返します.
  • async function fun() {}
    let result = fun()
    console.log(result)  //    fun        ,      Promise  
    
  • ルール2:awaitはasync関数で使用する必要があります.
  • は単独で使用できません.
    async fun() {
       let result = await Promise.resolve('success')
       console.log(result)
    }
    fun()
    
  • ルール3:awaitの後にPromiseオブジェクトが必要です.そうしないと意味がありません.awaitの後のPromiseオブジェクトはthenを書く必要はありません.awaitの役割の一つは、後のPromiseオブジェクトの成功状態から伝達されたパラメータを取得することです.
  • function fun() {
       return new Promise((resolve, reject) => {
           setTimeout(() => {resolve('hello javascript')})
       })
    }
    async fn() {
       let result = await fun() //fun     Promise  
       console.log(result)    //   Promise        'hello javascript'
    }
    
    fn()
    

    このように書くこともできますが、意味はあまりありません.
    let fun = async function() {
      let result = await 123
      console.log(result)
    }
    fun()
    

    構文
    async関数はPromiseオブジェクトasync関数の内部returnが返す値を返し、thenメソッドコールバック関数のパラメータです.
    async function  fun() {
        return 'hello node'
    };
    fun().then( (v) => console.log(v)) // hello node
    

    async関数の内部に異常が放出されると、返されるPromiseオブジェクトの状態がreject状態になります.投げ出されたエラーはcatchメソッドコールバック関数によって受信されます.
    async function fun(){
        throw new Error('error');
    }
    fun().then(v => console.log(v))
    .catch( e => console.log(e));
    

    async関数が返すPromiseオブジェクトは、内部のすべてのawaitコマンドのPromiseオブジェクトが実行されるまで待たなければなりません.すなわち、async関数内部の非同期操作が実行された場合にのみthenメソッドのコールバックが実行されます.次のコードを示します.
    const time = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
    async function f(){
        await time(1000);
        await time(4000);
        await time(7000);
        return 'finish';
    }
    
    f().then(v => console.log(v)); //   12s     'finish'
    

    通常、awaitコマンドの後ろにPromiseが付いていますが、そうでなければ、すぐにresolveに変換されるPromiseも次のコードで示されています.
    async function  fun() {
        return await 1
    };
    fun().then( (v) => console.log(v)) // 1
    

    rejectの状態を返すとcatchメソッドによって取得されます.
    async関数のエラー処理
    実はasync関数の文法は難しくなく、難しいのはエラー処理です.エラー処理については、上記のルール3で説明したようにawaitは、後のPromise成功状態伝達のパラメータを直接取得できますが、失敗状態はキャプチャされません.ルール1に従ってasync関数自体がPromiseオブジェクトを返すため、awaitを包むasync関数にthen/catchメソッドを追加することで解決します.
    まずdemoを見てみましょう
    let b;
    async function fun() {
        await Promise.reject('error');
        b = await 1; //   await    
    }
    fun().then(v => console.log(b));
    

    注意:async関数のawaitが1つだけreject状態になっている場合、後のawaitは実行されません.解決策:try/catchを追加できます.
    正しい書き方は以下の通りです.
    //      
    let b;
    async function fun() {
        try {
            await Promise.reject('error')
        } catch (error) {
            console.log(error);
        }
        b = await 1;
        return b;
    }
    fun().then(v => console.log(b)); // 1
    

    複数のawaitがある場合はtry/catchにすべて配置できます.エラー処理を含む完全なdemoを見てみましょう.
    let promise = new Promise((resolve, reject) => {
      setTimeout(() => {
          let random = Math.random()
          if (random >= 0.5) {
              resolve('  ')
          } else {
              reject('  ')
          }   
      }, 1000)
    })
    async function fn() {
      let result = await promise
      //result promise      ,     ,          catch 
      return result 
    }
    fn().then(response => {
      console.log(response) 
    }).catch(error => {
      console.log(error)
    })
    //        
    

    注意:上記のコードは2つの点に注意しなければならない.1つはasync関数がアクティブなreturnを必要とし、Promiseの状態が成功すれば、returnのこの値は次のthenメソッドによって捉えられる.二つ目はasync関数にエラーがあればcatchにキャプチャされます!
    同期と非同期
    async関数でawaitを使用すると、awaitのコードが同期します.awaitの後のPromiseの実行が完了してから結果が続くのを待つだけです.awaitは待機します.これで非同期は回避されますが、コードがブロックされるので、使用するときは周到に考慮する必要があります.demoを見てみましょう
    function fun1(name) {
      return new Promise((resolve, reject) => {
          setTimeout(() => {
              resolve(`${name}    `)
          }, 1000)
      })
    }
    async function testfun2() {
      let p1 = await fun1('  ')
      let p2 = await fun1('  ')
      let p3 = await fun1('   ')
      return [p1, p2, p3]
    }
    testfun2().then(result => {
      console.log(result)
    }).catch(result => {
      console.log(result)
    })
    

    このように書くのはokですが、awaitはコードをブロックし、各awaitは後続のfun 1()の実行が完了してから次の行のコードを実行しなければならないので、testfun 2関数の実行には3秒かかります.特定のシーンに遭遇しない場合は、そうしないほうがいいです.
    サイクルでasync/awaitを使用する
    ループでawaitを使用するには、async関数で使用する必要があります.for...ofでawaitを使用してdemoを見てみましょう.
    let fun3 = (time) => {
      return new Promise((resolve) => {
          setTimeout(() => {
              resolve(time)
          }, time)
      })
    }
    let times = [1000, 2500, 3000]
    async function testfun3() {
      let result = []
      for (let item of times) {
          let temp = await fun3(item)
          result.push(temp)
      }
      return result
    }
    testfun3().then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
    //     [ 1000, 2500, 3000 ]
    

    async非同期コールバック同時
    1リクエスト、2リクエストが同時に送信され、リクエストが到着する順序を規定します.もし私たちにこのようなビジネスニーズがあれば、2つのリクエストを同時に送信しますが、リクエストを受信する順序を規定するにはどうすればいいですか.ここではチェン一峰大神のコードを参考にします.demoを見てみましょう.
      // 1  
      function getData1 () {
        return new Promise(function (resolve, reject) {
          setTimeout(() => {
            console.log('1   ')
            resolve('       1')
          }, 2000)
        })
      }
      // 2  
      function getData2 () {
        return new Promise(function (resolve, reject) {
          setTimeout(() => {
            console.log('2   ')
            resolve('       2!)
          }, 1500)
        })
      }
      async function asyncDemo2 () {
        const arr = [getData1, getData2]
        const textPromises = arr.map(async function (doc) {
          const response = await doc()
          return response
        })
        //      
        for (const textPromise of textPromises) {
          console.log(await textPromise);
        }
      }
      // 2    (  2 1500ms   )   2   
      // 1   
      //        1  (for .. of )        
      //        2
    

    async/awaitに適したビジネスシーン
    フロントエンド開発では、複数のリクエストを送信する必要があり、後のリクエストの送信は常に前のリクエストが返したデータに依存する必要があるというシーンに遭遇することがあります.この問題については,Promiseのチェーン呼び出しで解決したりasync/awaitで解決したりすることができるが,後者はより簡潔になる.demo:promiseチェーン呼び出しを使用して次のコードを処理します.
    function fun2(time) {
      return new Promise((resolve, reject) => {
          setTimeout(() => {
              resolve(time)
          }, time)
      })
    }
    fun2(500).then(result => {
      return fun2(result + 1000)
    }).then(result => {
      return fun2(result + 1000)
    }).then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    }) 
    //      2500
    

    async/awaitを使用して処理します.
    function fun2(time) {
      return new Promise((resolve, reject) => {
          setTimeout(() => {
              resolve(time)
          }, time)
      })
    }
    async function getResult() {
      let p1 = await fun2(500)
      let p2 = await fun2(p1 + 1000)
      let p3 = await fun2(p2 + 1000)
      return p3
    }
    getResult().then(result => {
      console.log(result)
    }).catch(error => {
      console.log(error)
    })
    

    上記のコードから,thenを用いてチェーン呼び出しを継続するよりもasync/awaitを用いてより簡潔で明瞭で読みやすいことが分かる.
    Promiseに対するasyncの利点
  • thenチェーン
  • をよりよく処理できる
  • 中間値
  • デバッグPromiseより
  • デバッグが容易
    async/awaitの利点をまとめます.
  • 回調地獄の問題を解決した
  • 同時実行
  • をサポート
  • は、戻り値return変数
  • を追加することができる.
  • は、try/catchキャプチャエラー
  • をコードに追加することができる.