JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptにはsleepメソッドが存在しない?代わりに使えるPromiseとasync/awaitの使い方

他のプログラミング言語の経験があるエンジニアや開発者であれば、処理を一時停止させるためにネイティブのsleep()メソッドを使ったことがあるかもしれません。しかし、JavaScriptにはそのようなネイティブメソッドは存在しません。

この記事では、JavaScriptでスリープ機能をシミュレートする2つの方法——Promiseとasync/await関数について解説します。

非同期関数の基礎知識

Promiseやasync/awaitについて説明する前に、まずsetTimeout()メソッドに簡単に触れておきましょう。このメソッドを理解することで、なぜJavaScriptにPromiseが必要なのかが見えてきます。

JavaScriptはシングルスレッド言語

「JavaScriptはシングルスレッド言語である」とは、JavaScriptにはコールスタックが1つ、メモリヒープも1つしかないという意味です。大まかに言えば、JavaScriptはコードを1行ずつ順番に読み込み、次の行に進む前に現在のコードを実行し終える必要があります。このため、JavaScriptは本質的に同期(シンクロナス)的な言語です。しかし場合によっては、コードを非同期に動かすための工夫が必要になります。

同期コードと非同期コードの違い

次の例を見てみましょう。

// 同期処理
console.log("This will print first")
console.log("This will print second")
console.log("This will print third");

非常にシンプルですね。各console.logは、JavaScriptが順番に実行するため、記述した順に出力されます。

では、2行目の出力を3行目より先に行いたい場合はどうすればよいでしょうか。順番を無視してconsole.logを出力するには、setTimeout()を使います。

// setTimeout
console.log("This will print first")
 
setTimeout(() => {
console.log("This will print third")
}, 1000);
 
console.log("This will print second");

setTimeout()を使うと、スレッドをブロックせずに関数を実行できるため、他のコードを並行して動かすことができます。第1引数は、一定時間後に実行されるコールバック関数です。第2引数はミリ秒単位の数値で指定します。

setTimeout()メソッドは、他の言語にネイティブで備わっているsleepメソッドを次のように模倣します。

  1. JavaScriptエンジンがsetTimeout()関数を実行すると、バックグラウンドでタイマーがセットされる
  2. タイマーのカウントダウン中も、他のコードが実行され続ける
  3. タイマーが0になると、setTimeout()内のコールバック関数が実行される

setTimeout()の仕組みを理解することは、Promiseやasync/await関数の動作を理解するうえで非常に重要です。次はPromiseについて見ていきましょう。

Promise

Promiseの作成

Promiseは非同期処理を行うための仕組みです。Promiseコンストラクタは、resolveとrejectという2つのパラメータを持つコールバック関数を受け取ります。このコールバック関数内には、処理が完了したときにresolveまたはreject関数を呼び出し、レスポンスを渡すロジックを記述します。

console.log("before promise")
let promise = new Promise((resolve, reject) => {
   let resolvedFlag = false; 
// テスト用のフラグ。意図的に値を切り替えて動作を確認できる
   console.log("first");
   console.log("second")
   console.log("third")
   console.log("fourth");
   resolvedFlag = true; // すべてのconsole.logが完了したらtrueに切り替え
   
   if(resolvedFlag) { // trueならresolve関数を呼び出す 	
        resolve("Promise resolved");
        console.log("after promise");
 
 
   } else { // そうでなければErrorオブジェクト付きでreject関数を呼び出す
     reject(new Error("Promise failed")); 
     console.log("after promise");
   }
 });

このコードスニペットは、シンプルなPromiseを示しています。Promiseには3つの状態があります。

pending(保留) —— resolveもrejectもされていない状態。Promiseの初期状態です。

resolved(履行) —— 処理が正常に完了した状態。

rejected(拒否) —— 処理中にエラーが発生した状態。

上記のコードスニペットでresolvedFlagをtrueからfalseに切り替えてみてください。resolvedになったPromiseとrejectedになったPromiseのそれぞれの挙動を確認できます。

重要なのは、Promiseはスクリプトの実行を一時停止し、Promiseがresolveまたはrejectされるまで待機してから、処理を再開するという点です。

Promiseのレスポンスを利用する

Promiseのインスタンスを作成したら、then()catch()を使って、返されたPromiseからレスポンスを受け取った後のロジックを記述します。これは1つの文として記述でき、全体像は次のようになります。

promise.then(func).catch(func);

thenメソッドとcatchメソッドを呼び出す括弧の中には、レスポンスをパラメータとして受け取る匿名関数を記述します。

promise // 前のコードスニペットで作成したpromise
   .then(response => { // 成功レスポンスの場合ここに到達する
// 成功時のロジック
     console.log(response); 
     console.log("after promise");
   })
   .catch(error => { // エラーはここでキャッチされる
// エラー時のロジック
     console.log(error.message);
     console.log("after promise");
   })

Promiseは、データベースへのアクセスやHTTPリクエストを行う際によく使われます。

Async/Await関数

sleep()メソッドをシミュレートするもう1つの方法は、async/await関数を使うことです。本質的に、これらの非同期関数はPromiseで書くのと同じロジックを、より少ないコードで実現する別の書き方にすぎません。

asyncキーワードは、ES6以前の関数ではfunctionキーワードの前に、ES6以降のアロー関数ではパラメータを記述する位置の前に置きます。先に完了させておきたい処理は、このブロック内に記述します。

const firstFunction = () => { // 非同期で実行したいロジック
       let resolved = true;
      
       if(resolved) {
         let respObj = { message: "Resolved!"};   
         return respObj.message;
       } else {
         let errObj = { message: "Error!"};
         return errObj.message;
       }
     }
    
     const asyncExample = async () => { 
// パラメータを書く位置の前にasyncキーワードを配置
       console.log("Calling function...");
       let result = await firstFunction();
// awaitはロジックを含む関数の呼び出しの前に置く。
// 関数が完了するまで次の行には進まない。
       console.log(result);
       return result;
     }
    
     asyncExample() // async/await関数を呼び出す

async/await関数を使うと、ほぼ同じPromiseベースの非同期処理を、より少ないコードで実現できます。

まとめ

この記事では、他のプログラミング言語にネイティブで備わっているsleep()関数を模倣する方法を紹介しました。setTimeout()を使ってJavaScriptの非同期処理の概念を説明し、そのうえでPromiseとasync/await関数の使い方を見てきました。これらの手法を使いこなせるようになれば、JavaScriptでも処理のタイミングを柔軟に制御できるようになります。

  1. JavaScriptのpopメソッドとは?基本の構文と使い方をわかりやすく解説

    JavaScriptの配列には、データを操作するためのさまざまなメソッドが用意されています。その中のひとつがpopメソッドです。Web開発者は、配列から最後の要素を取り除きたいときや、取り除いた要素を使って何か処理を行いたいときに、このメソッドを活用します。本記事では、popメソッドの基本的な使い方を具体例とともにわかりやすく解説します。 popメソッドの構文 popメソッドの構文は非常にシンプルです。定義上、popメソッドは配列の最後の要素を削除し、その削除した要素を返す働きを持ちます。 let frozen = [ Anna, Elsa, Olaf, Kristoff, Sven ]

  2. JavaScript版のsleep()とは?自作sleep関数の実装方法を解説

    JavaScriptには、他の多くのプログラミング言語に存在するようなネイティブのsleep(スリープ)関数が用意されていません。しかし、いくつかのテクニックを使えば、この制限を簡単に回避することが可能です。最も手軽な方法のひとつが、setTimeoutとasync/awaitを組み合わせて、独自のsleep関数を作成することです。実装例const sleep = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)) // コールバックを使う場合 sleep(1000).then(()