ES 2017 async/await関数を使用する注意点


node 7.6.0正式にasync/await関数を実装するにつれて、jsの非同期プログラムは以前よりもっと簡単になります.しかし、私たちはasync/awaitを全面的に抱え込む前に、この特性について詳しく知る必要があります.
書き方
基本的に、どの関数もasync関数になります.以下は合法的な書き方です.
  • 関数宣言async function foo () {}
  • 関数式const foo = async function () {}
  • 方法定義const obj = { async foo () {} }
  • 矢印関数async () => {}
  • async関数は、常にPromiseに戻る.
    戻り値がprmitive値であっても、async関数はreturnを介して自動的に戻り値をPromiseオブジェクトに包装して返します.したがって、次の2つの関数は等価である.
    正常(Fulfill)
    // async  
    async function foo () {
      return 'a'
    }
    
    // Promise
    function foo () {
      return Promise.resolve('a')
    }
    異常(Reject)
    // async  
    async function foo () {
      throw new Error('error')
    }
    
    // Promise
    function foo () {
      return Promise.reject(new Error('error'))
    }
    なお、戻り値自体がPromiseオブジェクトである場合には、async関数のreturnは、戻り値を二回包装しない.awaitは常に順番に実行される.async関数を使用する前に、私たちはまたその動作メカニズムを解明しなければなりません.特に実行順序においては、完全に同期された思惟はasync関数に適用されないかもしれない.
    下記のコードを考慮します.
    function asyncGet (x) {
      return new Promise(resolve => setTimeout(() => {
        console.log('a')
        resolve(x)
      }, 500))
    }
    
    async function test () {
      console.log('b')
      const x = 3 + 5
      console.log(x)
    
      const a = await asyncGet(1)
      console.log(a)
    
      const b = await asyncGet(2)
      console.log(b)
    
      console.log('c')  
      return a + b
    }
    
    const now = Date.now()
    console.log('d')
    test().then(x => {
      console.log(x)
      console.log(`elapsed: ${Date.now() - now}`)
    })
    console.log('f')
  • async関数は、一般関数と同じ順序で実行され、await文が実行されると、Promiseオブジェクト
  • に戻る.
  • awaitは、asyncの関数を、待つPromiseがfulfillまたはrejectによって後でコード
  • を実行するまで保留すると理解できる.
  • async関数の戻り値は、一般Promiseと区別されていません.
    したがって、上のコード出力は
    d
    b
    8
    f
    a
    1
    a
    2
    c
    3
    elapsed: 1010
    注意dとfの中間出力
    Promiseを混ぜたバージョンをもう一つ見てみましょう.
    function asyncGet (x) {
      return new Promise(resolve => setTimeout(() => {
        console.log('a')
        resolve(x)
      }, 500))
    }
    
    async function test () {
      console.log('b')
      const x = 3 + 5
      console.log(x)
    
      const [a, b] = await Promise.all([
        asyncGet(1),
        asyncGet(2)
      ])
    
      console.log('c')  
      return a + b
    }
    
    const now = Date.now()
    console.log('d')
    test().then(x => {
      console.log(x)
      console.log(`elapsed: ${Date.now() - now}`)
    })
    console.log('f')
    出力結果
    d
    b
    8
    f
    a
    a
    c
    3
    elapsed: 509
    elapsedの違いに気づきましたか?これはなぜですか?awaitはいつも順番に実行すると言います.異なるawaitの間では並行して実行できないので、本当の完全非同期はPromise.allのような方法を借りなければなりません.
    async関数とcalbackawaitは、直接的な小包の関数asyncに影響を与えることができるだけである.したがって、calback関数のawaitは、async関数全体の実行を保留しない.
    よくある間違い
    async function getAll (vals) {
      return vals.map(v => await asyncGet(v))
    }
    このコードには文法的なエラーがあり、awaitasync関数の内部にありません.mapのcalbackにasyncを加えれば?
    async function getAll (vals) {
      return vals.map(async v => await asyncGet(v))
    }
    このコードは実行できますが、まだ二つの問題があります.
  • Promiseオブジェクトの配列を返します.期待されるvalue配列
  • ではありません.
  • awaitmapのcalbackを一時停止するだけですので、mapが完成した時には、asyncGetも全部完成したとは保証できません.
    正しい書き方はPromise.allを借りなければなりません.
    async function getAll (vals) {
      return Promise.all(vals.map(v => asyncGet(v)))
    }
    締め括りをつける
    上から見れば、Promiseasync関数の基礎であり、async関数を楽しく使用するには、Promiseについての比較的深い理解が必要である.いくつかの一般的なタスクも、async関数だけでは実現できません.この文章を読んでから、asyncの関数に対してもっと全面的な認識を持っていただきたいです.そうすれば、もっと使いやすくなります.