MENU

JavaScript同期・非同期タイマー処理

【Node.js】 PromiseベースのタイマーsetTimeoutとsetInterval

更新日:2021/08/26

 

JavaScriptにはタイマー監視に関するsetTimeoutとsetIntervalメソッドが用意されています。
これらはコールバック関数により通知されます。

 

Node.jsにもこの機能がありますが、Promise機能を使用したタイマー監視も用意されていて、見通しの良いコード作成に役立てることができます。

 

ここでは、Promise機能を使用したタイマーについてお伝えします。

 

setTimeoutとsetInterval

 

setTimeoutとsetIntervalは、JavaScriptでタイマー監視をおこなうために、監視時間とコールバック関数を登録するメソッドです。

 

詳細は別の記事で解説しているので、そちらをご覧ください。

 

【JavaScript】 タイマー処理/setTimeoutとsetInterval

 

なお次項で紹介するPromise版のsetTimeoutとsetIntervalは、通常版のsetTimeoutとsetIntervalと同様にタイマー遅延が発生します。

 

理由は、上記リンク先をご覧ください。

Promise版タイマーメソッドの読み込み

 

Node.jsは通常のsetTimeoutとsetIntervalメソッドの他に、Promiseを返すsetTimeoutとsetIntervalメソッドが用意されています。

 

Promise版タイマーメソッドはv15.0.0より導入されました。それ以前のバージョンでは使用できません。

 

これらのメソッドを使用することで、見通しの良いコードを作成することができます。

 

Promise版のsetTimeoutとsetIntervalメソッドは、timers/promisesモジュールに含まれています。
プログラムで使用するためには、importで読み込む必要があります。

 

Promise版タイマーメソッドのimport読み込み

 


import { setTimeout , setInterval } from "timers/promises";

 

importでエラーが出るときは、requireを使用します。

 

Promise版タイマーメソッドのrequire読み込み

 


const { setTimeout , setInterval } = require("timers/promises");

 

Promise版setTimeout/setInterval

 

Promise版setTimeoutとsetIntervalは、通常のsetTimeoutとsetIntervalと引数が異なります。

 

構文

 

Promise版setTimeout/setIntervalの構文

 

setTimeout( 指定時間 , 値 , オプション )
setInterval( 指定時間 , 値 , オプション )

 

 

引数

 

■指定時間

 

省略可能です。省略時の規定値は1です。
指定時間はミリ秒で指定します。

 

指定時間以上の時間が経過すると、Node.jsのシステムがsetTimeoutで返されたPromiseオブジェクトを解決させます。

 

■値

 

省略可能です。
Promiseオブジェクトの解決結果として引き渡される値です。

 

通常版のsetTimeoutはコールバック関数に渡す引数を指定できますが、同じような意味合いで使用できます。

 

■オプション

 

省略可能です。
使用方法は後述します。

 

戻り値

 

setTimeoutとsetIntervalは戻り値が異なります。

 

■setTimeout

 

Promiseオブジェクトを返します。
thenやcatchメソッド、async/awaitキーワードなどが使用できます。

 

■setInterval

 

非同期イテレーターを返します。
for-await-of構文などで処理をする必要があります。

 

Promise版setTimeoutの使用方法

 

 

Promise版setTimeoutの使用方法を紹介します。

 

thenでの処理

 

次のコードは3秒以上経過を待ち、実際の経過時間をコンソールに出力する処理を3回おこなっています。

 

3秒以上経過を待つ

 


const { setTimeout } = require("timers/promises");

const thenFunc = value =>{
    console.log( Date.now() - value.startTime + "ミリ秒経過" );
    return setTimeout( value.wait , value );
};

const timerData = { wait : 3000 ,  startTime : Date.now() };

setTimeout( timerData.wait , timerData )
    .then( thenFunc )
    .then( thenFunc )
    .then( thenFunc );

// 結果:
// 3008ミリ秒経過
// 6031ミリ秒経過
// 9038ミリ秒経過

 

このコードは問題点が一つあります。
3回目のthenで呼び出される関数内でsetTimeoutを実行している点です。

 

本来なら3回目の出力でNode.jsの処理が終わるべきですが、タイマーを登録しているため3秒以上経過後に終了します。
最後だけ処理を変えるなどの工夫が必要です。

 

async/awaitを使用

 

async/awaitを使用することで、見かけ上一時停止するコードを作成できます。

 

見かけ上一時停止するコード

 


const { setTimeout } = require("timers/promises");

const sleep = waitTime => setTimeout( waitTime ) ;

const aFunc = async function( ){

    // なんらかの処理

    await sleep( 3000 ); // 3秒(以上)停止

    // なんらかの処理

    await sleep( 1000 ) ;// 1秒(以上)停止

    // なんらかの処理

}
aFunc();

 

sleepについては次の記事で紹介しています。
【JavaScript】 一定時間停止(sleep)のやりかた

Promise版setIntervalの使用方法

 

for-await-of構文

 

Promise版setIntervalの戻り値は、非同期イテレーターです。
非同期イテレーターは、for-await-of構文で順番に値を処理していきます。

 


const {setInterval} = require("timers/promises");

(async ()=>{
    for await ( const startTime of setInterval( 3000 , Date.now() ) ){
        console.log( Date.now() - startTime + "ミリ秒経過" );
    }
})();

 

for-of構文(動作しない)

 

for-of構文のステートメント内でawaitを使用する方法も考えられます。

 


const {setInterval} = require("timers/promises");

(async ()=>{
    for  ( const timerPromise of setInterval( 3000 , Date.now() ) ){
        const startTime = await timerPromise;
        console.log( Date.now() - startTime + "ミリ秒経過" );
    }
})();

 

setIntervalの結果からうまく値が取り出せないためか、ステートメント内部に処理が移りません。

 

for-of構文は非同期イテレーターに対応していないため、そもそも使用できません。
次のように、async関数の外で実行すると、エラーが発生します。

 


for  ( const timerPromise of setInterval( 3000 , Date.now()  ) ){
    console.log( timerPromise );
}
// TypeError: setInterval is not a function or its return value is not iterable

 

async関数内部で使用するとエラーが表示されず、さらにステートメントに処理が移らないというとても困ったことになります。
setIntervalでfor-ofは使用しないようにしましょう。

 

 

nextメソッドを呼び出す

 

タイマーを個別にawaitしたいときは、非同期イテレーターのnextメソッドを呼び出します。

 


(async ()=>{

    const si = setInterval( 3000 , Date.now() );

    let result = await si.next();

    while( !result.done ){
        console.log( Date.now() - result.value + "ミリ秒経過" );
        result = await si.next();
    }
})();

 

3番目の引数について

 

Promise版setTimeoutとsetIntervalは、3番目の引数としてオプション値を指定できます。

 

オプション値を次のオブジェクトで指定します。

 

オプション値の形式

 

{
ref : ブール型
signal : AbortSignalオブジェクト
}

 

 

オプションref:タイマー監視の動作

 

オプションrefは、イベントループ中に他のイベントが完了してタイマー監視のみ残っているとき、どのような動作をするかを指定します。

 

true: タイマー監視を続けます
false: タイマー監視を中止し、プロセスを終了します。

 

規定値はtrueです。

 

次のコードは、タイマーの監視をせずに終了します。

 


const {setTimeoutl} = require("timers/promises");

setTimeout(3000,null,{ref:false})
    .then( ()=>console.log( "3秒経過" ) );

 

オプションsignal:タイマーのキャンセル

 

オプションsignalは、タイマー監視をキャンセルするために必要なAbortSignalを指定します。

 

詳しくは、次項で解説します。

Promise版タイマーのキャンセル

 

Promise版タイマーをキャンセルする必要があるときは、3番目の引数にAbortSignalを指定します。

 

次の手順で処理をします。

 

(1) new AbortController() で アボートコントローラーを作成する。
(2) タイマーの第三引数にオブジェクト{ signal: アボートコントローラー.signal } を指定する
(3) キャンセルしたいタイミングで、アボートコントローラー.abort() を実行する

 

キャンセルをおこなうと、エラーが発生します。
catch句で補足してください。

 

 

Promise版setTimeoutのキャンセル

 

次のコードは3秒と5秒のタイマーを用意して、3秒経過後に5秒のタイマーをキャンセルしています。

 


const { setTimeout } = require("timers/promises");

const abortController = new AbortController();

  // 3秒タイマー
setTimeout(3000,"3秒経過")
    .then( value=>{
        console.log( value );
        abortController.abort(); // 5秒タイマーをキャンセル
    } );

  // 5秒タイマー
setTimeout(5000 , "5秒経過", { signal: abortController.signal })
    .then( console.log )
    .catch( () => {
        console.log( 
             abortController.signal.aborted 
                  ? "中断されました" : "不明のエラー"
         );
    } );

 

最後のcatch()を忘れると、次のエラーが発生します。

 

AbortError: The operation was aborted

 

キャンセルを行った場合、abortController.signal.aborted がtrueになります。
キャンセル以外でcatch句が捕捉される可能性がある場合は、この値を確認してください。

 

Promise版setIntervalのキャンセル

 

setIntervalのキャンセルは次の二つのケースが考えられます。

 

(1) タイマー経過後の処理を行った後、次のタイマー待ちをキャンセルする

 

(2) 現在のタイマー待ちをキャンセルする。

 

この二つについて、順番に解説していきます。

 

 

次のタイマー待ちをキャンセルする

 

for-await-ofでの繰り返し処理は、breakで終了することができます。

 

次のコードは、3秒間隔で処理を繰り返しています。
処理後、総時間が10秒を超えているかをチェックし、超えていたらfor-await-ofを抜けます。

 

for-await-ofの中断

 


const { setInterval } = require("timers/promises");

(async ()=>{
    for await ( let startTime of setInterval( 3000 , Date.now() ) ){
        const time = Date.now() - startTime;
        console.log( time + "ミリ秒経過" );

        if( time > 10000 ) break; // for-await-ofを抜ける
    }
    console.log( "タイマーを終了しました" );
})();

 

現在のタイマー待ちをキャンセル

 

タイマー監視中にキャンセルするときは、setTimeoutのケースと同じように、3番目の引数にAbortSignalを指定します。

 

次のコードは、10秒経過した時点でsetIntervalでのタイマー監視をキャンセルしています。

 

現在のタイマー待ちをキャンセル

 


const { setTimeout , setInterval } = require( "timers/promises");

const abortController = new AbortController();

// キャンセルをおこなう10秒タイマー
setTimeout(10000,"10秒経過")
    .then( value =>{
        console.log( value + "→アボートします" );
        abortController.abort();
    } );

(async ()=>{
    try {
        for await ( let startTime of setInterval( 3000 , Date.now() , { signal: abortController.signal }) ){

            console.log( Date.now() - startTime + "ミリ秒経過" );

        }
    }catch( e ){
        console.log(
            abortController.signal.aborted
                ? "中断されました" : "不明のエラー"
        );
    }
})();

 

ポイントは、abortController.abort()を実行するとエラーが発生するので、try-catchを使用している点です。

 

try-catchを使用したくないときは、Promiseのcatchでエラーを捕捉できるように、nextメソッドを呼び出して手動でループします。

 


(async ()=>{

    const si = setInterval( 3000 , Date.now(), { signal: abortController.signal });

    let result = await si.next().catch(e=>({ done:true }) );

    while( !result.done ){
        console.log( Date.now() - result.value + "ミリ秒経過" );
        result = await si.next().catch(e=>({ done:true }) );
    }
    console.log(
        abortController.signal.aborted
            ? "中断されました" : "不明のエラー"
    );

})();

けーちゃんおススメJavaScript入門書

  • スラスラ読める JavaScript ふりがなプログラミング
  • プログラム未経験者がJavaScript始めるならコレ!
    コードを掲載して自分で理解しろという投げっぱなしな入門書とは異なり、コードに一つ一つどんなことをやっているかをふりがなという形式で解説しています。
    それでいてJavaScriptの基礎と応用を学べる良書です。
  • これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
  • JavaScriptの機能を実践で活かすにはHTMLやCSSの知識が不可欠です。
    しかしそれらの知識があることが前提として書かれている書籍が多い中、この本は総合的な知識を身に着けることができます。
    HTMLやCSSの知識も不安な方には、ぴったりの一冊です
  •  

    入門書の役割は、自分のやりたいことをネットで調べることができるようになるための、基礎的な知識の獲得です。
    まずはこれらの本でしっかりと基礎知識を身につけましょう。
    そしてもっと高度なことや専門的なことはネットで調べ、情報が足りないと感じたら書籍を購入してください。


    記事の内容について

     

    こんにちはけーちゃんです。
    説明するのって難しいですね。


    「なんか言ってることおかしくない?」
    たぶん、こんなご意見あると思います。

    裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。

    そんなときは、ご意見もらえたら嬉しいです。

    ご意見はこちら。
    https://note.affi-sapo-sv.com/info.php

    【お願い】

    お願い

    ■このページのURL


    ■このページのタイトル


    ■リンクタグ


    ※リンクして頂いた方でご希望者には貴サイトの紹介記事を作成してリンクを設置します。
    サイト上部の問い合わせよりご連絡ください。
    ただしサイトのジャンルによっては、お断りさせていただくことがあります。