開発

for await of を使ってみる

以前async/awaitとmapメソッドに関して記事を書いたのですがJavaScriptも発展してasync/awaitに関連した構文、for await of文が使えるようになったので試してみました。

Async/Awaitとmapメソッドを組み合わせたときの動作 – Mark Creators

年末年始の休みを利用してなんとかElectronアプリを作り終えようと日夜励んでいる今日此の頃です。基本的に昔のJavaScriptの知識しかないので正に旧石器 async, await, javascript, map, node.js, メモ

for await of文 はES2018から使用できるようです。Node.jsではバージョン10.3.0から使用できるようです。あるいは古いバージョンでは–harmonyフラグを付けると使えるかもしれません。

この記事ではNode.jsバージョン12.0.0 を前提で書きます。

以前のasync/await記事をベースに試していきたいと思います。以前はどのようなサンプルコードを書いたかというと

function heavyProc(i) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(i)
    }, 1000 - i * 20)
  })
}

async function main3b() {
  const ary = [1,2,3,4,5]
  let n
  let total = 0

  console.log('BREAK1')
  for (const i of ary) {
    n = await heavyProc(i)
    total += parseInt(n)
    console.log('n ', n)
    console.log('total ',total)
  }

  console.log('BREAK2')

  for (const i of ary) {
    n = await heavyProc(i)
    total += parseInt(n)
    console.log('n ', n)
    console.log('total ',total)
  }
  console.log('BREAK3')
}
main3b()

***RESULT***

BREAK1
n  1
total  1
n  2
total  3
n  3
total  6
n  4
total  10
n  5
total  15
BREAK2
n  1
total  16
n  2
total  18
n  3
total  21
n  4
total  25
n  5
total  30
BREAK3

というようなものでした。

これは非同期な関数の処理待ちをasync/awaitで対処するという使い方でした。関数の引数は、ループ処理で配列の値を都度渡すという動きになっていました。この非同期な関数は、引数は何でも良くて呼び出しの順番が変わっても最終的なtotalの値は同じになります。なので、わざわざ順番通りに実行しなくても非同期で並列実行しても結果は同じになります。

自分でサンプルコードを書いておいてアレですがなんでこんな面倒くさい順番通りな処理しているのかな、とか思っていたのです。結果は同じなのに。

ただ世の中には順序が大事な処理が必要な場面も出てきます。例えば、リモートのデータベースからデータを取り出した時に件数が200件ぐらいあったけど、とりあえず最初のページで25件を表示したい、nextボタンがクリックされたら次ページでさらに次の25件を表示したい、というような処理でしょうか。

この場合、メインの処理は最大25件をひとまとまりで表示するループ処理にしておいて関数やメソッド側で何件までメイン処理側にデータを渡したとかの状態やデータベースとの非同期なやり取りを任せてしまうとスッキリしそうです。

おお、順序性が大事な非同期処理が必要な場面が出てきました。この処理はイテレータジェネレーター関数を使うと楽に実装できそうです。また、メインのループ判定は非同期的に決定されるのでメインループも非同期処理の対処が必要です。そこで使えそうなのが for await of 文になります(無理やり話を持ってこれた)

ということで上のサンプルコードに変更を加えてみたいと思います。処理内容は変えませんので上記のデータベース云々を脳内変換していただければ。

まずはheavyProcからアップデートします。

async function* genHeavyProc(i) {
  while(i <= 5) {
    yield new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(i)
      }, 1000 - i * 20)
    })
    i++
  }
}

新しいジェネレーター関数genHeavyProcを使っていますが呼び出したときの動作としては同じです。便宜上最大5回だけ呼び出せるようにしています。次はメイン処理です。

async function main5a1() {
  // const ary = [1,2,3,4,5]
  let n
  let total = 0
  const gen = genHeavyProc(1)

  for await (const i of gen) {
    total += parseInt(i)
    console.log('n ', i)
    console.log('total ',total)
  }

}
main5a1()

引数の配列をループで回していた部分をイテレータで回すようにしています。ハイライトの部分(for await of)が今回の要の部分になります。結果は下記のようになります。

n  1
total  1
n  2
total  3
n  3
total  6
n  4
total  10
n  5
total  15

少しの変更だけで書き換えることができました。結果としては変更前と変わらないですがメイン処理で引数の配列を管理しないで良くなりました。この引数がどのページを処理するかというようなものだとしたらメイン処理の中で気にしないで良くなったということです。イテレータの中で管理できるようになり、かつ非同期的に扱えるようになったということです。

管理人

Recent Posts

RustでSOLID原則について考える

binllionを作っていて1…

2か月 ago

[binllion] clippyを使ってコードを改善する

ここままでエディターとしての最…

3か月 ago

[binllion] データのエクスポート機能を追加する

インポートとくればエクスポート…

3か月 ago

[binllion] データのインポート機能を追加する

データの編集機能の開発も終えた…

3か月 ago

[binllion] 編集用バッファに削除機能を追加する

上書き、挿入、とくれば削除も必…

3か月 ago

[binllion] 編集用バッファに挿入機能を追加する

上書き、とくれば挿入も必要でし…

3か月 ago