1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaScriptのgeneratorについて part2

Last updated at Posted at 2022-08-20

初めに

今回は[Symbol.asyncIterator]、非同期ジェネレータについてまとめていきたいと思います。

参考文章はこちらです。
Async iteration and generators - javascript.info
Symbol.asyncIterator - MDN

[Symbol.asyncIterator]

let range = {
  from: 1,
  to: 5,

  [Symbol.asyncIterator]() {
    return {
      current: this.from,
      last: this.to,

      async next() {
        await new Promise(resolve => setTimeout(resolve, 1000));

        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

// IIFE
(async () => {
  for await (let value of range) {
    console.log(value)
  };
})()

参考文章からの例です。最近の勉強では初見の書き方が出てきたり、既定の書き方で書かねば動かないというのがよくあるので、下調べしてから自分なりに解釈してメモします。
今回の[Symbol.asyncIterator]の非同期反復処理も既定の書き方で、

  • 中身はnext()メソッドを内蔵するオブジェクトを返す。
  • next()ではPromiseオブジェクトを返す。あるいはasync next()で返すオブジェクトをPromiseに保証する。

自分のテストでは二番目がnext()からasyncawaitを除いてもちゃんと動けますが、しかし非同期という目的が実現できません。ここはawaitnext()毎回の反復処理がnew Promise(resolve => setTimeout(resolve, 1000))が終わるまで待ってから、下のコードを進むのです。

Using javascript's Symbol.asyncIterator with for await of loop - stackoverflowを参考して、next()を使わないように書いてみました。

// generator is iterable object
async function* counter() {
  for (let i = this.from; i <= this.to; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i
  }
};

let range = {
  from: 1,
  to: 5,
  [Symbol.asyncIterator]: counter
};

async function test() {
  for await (let value of range) {
    console.log(value)
  }
};

test()
  • [Symbol.asyncIterator]には反復可能のオブジェクト。([Symbol.asyncIterator](){}の書き方ならオブジェクト返す関数にする。)
  • 非同期の実現はasync generatorawaitsetTimeout()などの遅延処理メソッドと併用し、そしてasync functionfor await (let item of iterable)で結果をコンソール。

[Symbol.asyncIterator]にはオブジェクト、async generatorにはyieldで値の転送、async functionfor await (let item of iterable)でジェネレータからのプロミスオブジェクトを待つ。
...いろいろと書き直してみたいけど、書き方が限られてる気がします。

下は参考文章からの書き方です。

let range = {
  from: 1,
  to: 5,

  async *[Symbol.asyncIterator]() {
    for (let value = this.from; value <= this.to; value++) {
      await new Promise(resolve => setTimeout(resolve, 1000))

      yield value
    }
  }
};

(async () => {
  for await (let value of range) {
    console.log(value)
  }
})()

async *[Symbol.asyncIterator](){}forループを包んで、awaitで遅延処理を待ち、yieldで値を返す。下のIIFEでプロミスオブジェクト返すのを待って値をコンソール。
こちらのほうが読みやすい書きやすくと思います。

real-life example: paginated data

これもまた参考文章からの例です。

async function* fetchCommits(repo) {
  let url = `https://api.github.com/repos/${repo}/commits`

  while (url) {
    const response = await fetch(url, {
      headers: { 'User-Agent': 'Our script' }
    })

    const body = await response.json()

    let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/)

    nextPage = nextPage?.[1]

    url = nextPage

    for (let commit of body) {
      yield commit
    }
  }
};

(async () => {

  let count = 0

  for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) {

    // console.log(commit.author)
    console.log(commit.author.login)

    if (++count == 10) {
      break
    }
  }
})()

全体のプロセスとしては、
url確定

fetch()から返されたレスポンスを待つ

レスポンスをJSONにパースしたり、
ページ作りのためレスポンスからHeaders.get()を使い、キーでLinkオブジェクトを選択し、中に/<(.*?)>; rel="next"/に一致した値を取り出しnextPageへ。
nextPageが存在したら(null/undefinedでなければ)、Cache.match()で返されたPromise(ここは配列)インデックス[1]のリンクを取りurlに書き換えて、全部が終わったら次のwhileループへ。

レスポンスの全体からオブジェクト単位yield経由で値を転送し、キーで特定範囲の文字列を取り出す。(ここはコンソール、ほかにHTMLに戻すとかも。)
1ページの表示上限を設定する。

これまでの感想やまとめ

APIドキュメントを参考しながら、要求に応じたresponseをJSONにパースしJSONから一定の規律を見つけて、処理の仕方やHTMLへどう表現していくを考えるとか、一定の順序で進めば難しくないですが。
以前もrequire()でレスポンスからheaderやstateCodeなど取得して、それに対応するコードを実践したことがあるけれど、最近勉強メモの文章を書きながらやはり自分がHTTPリクエストへの認識が浅はかだなあと思います。
例えばrequestの処理の違いと、headerに注意すべきところ('User-Agent'が偽造できる問題とか)、CORSを利用してCSRF/XSS防止するなど、今の自分は基礎どころか入門レベルと思います。
より安全なWeb Appを作るためにこれからJavaScript以外、ネットワーク特にHTTP関連についてまとめていきたいと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?