Help us understand the problem. What is going on with this article?

ES2017 async/await で sleep 処理を書く

async/awaitES2016 でなく ES2017 でした。以下修正しています。

Node.js v7 より ES2017 async/await が利用できるとの話を耳にしたので「サーバサイドでくらい ES2017 使ってみるかな」とちょいちょい使い始めています。どこかのサーバへクエリを叩いてその結果をさらに別のサーバへのクエリに使うという処理って、手元でちょっとしたツールを作るときにも大いに登場する処理なのですが、async/await が使えることでとても簡単に書けるようになったので、その使用感などを僭越ながら簡単に紹介していきたいと思います。

単純なスリープ処理

始めに感動したのは async/await を使うとスリープ処理がいとも簡単に書けるというところでした。async/awaitPromise を色々とこねくり回したところ msec => new Promise(resolve => setTimeout(resolve, msec)); という記述で sleep 処理が書けるのだな、という結果に。

async-sleep.js
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

(async () => {
  console.log('スタート');
  await sleep(1000);
  console.log('1秒経ってる!')
})();

これを --harmony-async-await オプションをつけて実行すると、console.log 通りの表示になります。

$ node --harmony-async-await async-sleep.js
スタート
1秒経ってる!

このあたり Promiseとasync-awaitの例外処理を完全に理解しよう にはとても理解を助けられました。Promise から値を取り出すまで処理をストップするのが await で、その await で全体の処理がブロックされないように async で定義した非同期関数内でしか await を使えなくなっていますよ、というのが現状の僕の理解です。

JavaScript ってシングルスレッドのためか、処理をブロックさせないためにとことん非同期領域に処理を投げて、そのせいで連続する処理の記述が面倒になってる節があると思うのですが、async/await も例に漏れずといった印象。でも今までよりはずっと素敵です。ぜひブラウザでも使えるようになって欲しい。

スリープを利用したインターバル処理

さて、Promiseasync/await の関連が理解できてきたのが気持ちよくて、もう一例作ってみました。今度は二つの処理のレーシング(?)を利用したインターバル処理の例です。さっき定義した sleep 処理を再利用します。

async-interval-repeater.js
// さっきと同じ sleep 処理
const sleep = msec => new Promise(resolve => setTimeout(resolve, msec));

// 最低間隔を確保したいタイプのリピート処理を行う関数
const intervalRepeater = async (callback, interval) => {
  while (true) {
    const startTime = Date.now(); // 時間計測用
    console.log('処理を始めるよ!')

    // 本処理と sleep を同時実行して最低間隔を確保する
    await Promise.all([callback(), sleep(interval)])

    console.log('処理が終わったよ! 経過時間[ms]:', Date.now() - startTime)
  }
}

// resolve までに数秒(ランダム)かかるクエリのダミー
const dummyQuery = () => sleep(Math.random() * 2000);

// 最低でも 1秒 の間隔を挟みながらクエリを送る処理
intervalRepeater(dummyQuery, 1000);

ループで連続した処理を平易に書けるのがうれしいですね。

これもさっきと同様に実行すると

$ node --harmony-async-await async-interval-repeater
処理を始めるよ!
処理が終わったよ! 経過時間[ms]: 1014
処理を始めるよ!
処理が終わったよ! 経過時間[ms]: 1005
処理を始めるよ!
:
:

このように最低間隔 1秒 が確保されたリピート処理が実装できます。

この処理の用途として何を想定したかと言うと、とあるAPIを定期的に叩くような場合に欲しくなったりする

  • 前の処理の完了を待って次の処理を行う
  • 処理の最低間隔を確保する

の二つを両立したい場合です。前者だと Promise チェーンで処理を連続させることもできますが、たまにクエリの結果が空だったりすると処理が爆速で終了して、必要以上の間隔でクエリが再送されてしまったりします。また、後者の場合 setInterval を利用することも考えられますが、前の処理の完了を待たずに次の処理が始まってしまう処理のオーバーラップが発生したりしていました。連続するレコードを処理する場合など、処理のオーバーラップはできれば避けられたら考慮事項が少なくなるな、なんて場合も結構多い気がします。

これまで同様の処理を実装しようとすると limiter などの毎秒クエリ数を制限するライブラリでクエリ処理をラッピングしたりしていましたが、async/await を利用するとそうした工夫からも解放され、リモートリソースを叩くようなツールがとても快適に作れるようになるんですね。

...という初めての Qiita でした。何も書けずに手をこまねいている期間が長くなってしまったので、簡単な記事でも書いてみねば、と思いきって書いちゃいました。

他の方の記事と被っていたり邪魔だったりしたらすみません、生温かく注意していただけると嬉しいです。

asa-taka
iij
日本のインターネットを支えてきたIIJ。現在もその先もイニシアティブをとり続けます。
https://www.iij.ad.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした