JavaScript で関数の終了を待つ

Moataz Farid 2023年10月12日
  1. JavaScript の SyncAsync 関数
  2. JavaScript で関数の終了を待つには callback を使用する
  3. JavaScript で関数の終了を待つには promises を使用する
  4. 実行を続ける前に関数の終了を待つには、async/await を使用する
JavaScript で関数の終了を待つ

このチュートリアルでは、JavaScript の CallbacksPromisesAsync/await を紹介し、実行を継続する前に非同期関数が終了するのを待つ方法を示します。

PromisesAsync/await が何であるかを理解するためには、まず JavaScript の SyncAsync 関数が何であるかを理解する必要があります。

JavaScript の SyncAsync 関数

同期プログラミングでは、1つのコマンドを一度に実行します。ロングランアクションを実行する関数を呼び出すと、それが終了するまでプログラムを停止します。

JavaScript は伝統的にマルチコアでもシングルスレッドです。メインスレッドと呼ばれる単一のスレッドでのみタスクを実行させることができます。

このような同期動作はマルチスレッドの制限要因ですが、同時実行の問題を気にすることなくコードを書くことができます。

非同期プログラミングでは、ロングランアクションはメインスレッド以外の別のスレッドで実行されるため、メインの実行がブロックされることはありません。長時間実行している関数が終了すると、メインプログラムに通知され、その結果にアクセスすることができます。

JavaScript のほとんどの I/O プリミティブはノンブロッキングです。ネットワークリクエストやファイルシステムの操作は、すべてノンブロッキング操作です。JavaScript では、ブロックされることは例外です。

JavaScript は非同期プログラミング技術に基づいているので、callbackspromisesasync/await のような複数のアプローチがあり、関数を順番に実行できるようになっています。これにより、あるコードブロックや関数が他の特定の関数が終了する前に実行されることはありません。

JavaScript 同期と非同期

上の図は、2つの関数の非同期実行と同期実行の違いを明確に示しています。

JavaScript で関数の終了を待つには callback を使用する

同期文があれば、それらの文をお互いの後に実行するのは簡単です。

function one() {
  console.log('I am function One');
}
function Two() {
  console.log('I am function Two');
}

one();
Two();

出力:

I am function One 
I am function Two

例えば、functionOne()functionTwo() の 2つの関数を実行し、functionTwo() の中で非同期文を実行した後に functionOne() を実行するとします。

function functionOne(_callback) {
  // do some asynchronus work
  _callback();
}

function functionTwo() {
  // do some asynchronus work
  functionOne(() => {
    console.log('I am a callback');
  });
}

functionTwo();

上記のコードを実行すると、コンソールに最後に表示されるのは I am a callback です。callback の有名な例としては、setTimeout 関数とタイミングアウト後に実行するハンドラ関数があります。

function testCallBack() {
  console.log('log before use setTimeout function');
  setTimeout(() => {
    console.log('inside timeout');
  }, 5000);
  console.log('log after use setTimeout function');
}

testCallBack();

出力:

log before use setTimeout function
log after use setTimeout function
inside timeout

JavaScript で関数の終了を待つには promises を使用する

promise は非同期操作の最終的な履行や失敗を表すオブジェクトです。私たちは、promise に 1つ以上の then 文で処理の実行コールバックを付け、エラーハンドラのコールバックを catch で呼び出すことができるようにします。

doFirstThing()
    .then(result => doSecondThing(result))
    .then(newResult => doThirdThing(newResult))
    .then(finalResult => {
      console.log('The final result thing' + finalResult);
    })
    .catch(failureCallbackfunction);
}

上の例のように then 文と catch 文を連鎖させることができるのはプロミスの利点の一つです。上の例のように then 文と catch 文を連鎖させることができるのは、プロミスの利点の一つです。then の引数はオプションですが、結果を返さなければならない場合には必須です。

エラーが発生した場合、ブラウザはチェーンの最後に catch を探して実行します。有名な try-catch と非常によく似ています。

次の例は promises チェーンを理解するのに役立ち、実行を続ける前に非同期動作を持つ関数の実行が終わるのを待つ方法を示します。

var resolvedFlag = true;

let mypromise = function functionOne(testInput) {
  console.log('Entered function');
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('Inside the promise');
      if (resolvedFlag == true) {
        resolve('Resolved');
      } else {
        reject('Rejected')
      }
    }, 2000);
  });
};

mypromise()
    .then((res) => {console.log(`The function recieved with value ${res}`)})
    .catch((error) => {
      console.log(`Handling error as we received ${error}`);
    });

出力:

Entered function
Inside the promise
The function received with value Resolved

プロミスの作成は、新しい Promise() を返すのと同じくらい簡単です。コンストラクタ Promise() は関数を引数として受け取り、その関数には resolvereject の 2つのパラメータを指定します。

イベントが成功して結果を返す必要がある場合は、resolve() 関数を用います。しかし、エラーが発生して処理する必要がある場合は、reject() を用いてエラーを catch に送る。

フラグ resolvedFlag = true を設定して catch でのエラー処理をシミュレートします。resolvedFlagfalse に設定されている場合、関数 reject() が呼び出され、エラーは catch ブロックで処理されます。

出力:

Entered function
Inside the promise
Handling error as we received Rejected

実行を続ける前に関数の終了を待つには、async/await を使用する

JavaScript の非同期環境で実行を継続する前に関数の実行を待つ別の方法として、async/wait を利用する方法があります。

async 関数は async キーワードで宣言された関数ですが、async 関数の内部では await キーワードのみが許可されており、プロミスベースの非同期処理が実行されるか拒否されるまで非同期関数の内部での進行を中断するために利用されます。

asyncawait キーワードは、よりクリーンなスタイルでプロミスベースの非同期動作を可能にします。

それでは、async/await がどのように動作するかを理解しましょう。待っている関数は、それを呼び出す前に await キーワードを使って実行されるのを待つために Promise クラスのインスタンスを返さなければなりません。上述したように、await 文を含む関数は async 文と一緒に宣言しなければなりません。

以下の例は、実行を継続する前にそのプロミスベースの関数が終了するのを待つ方法を示しています。

function testAsync() {
  return new Promise((resolve, reject) => {
    // here our function should be implemented
    setTimeout(() => {
      console.log('Hello from inside the testAsync function');
      resolve();
      ;
    }, 5000);
  });
}

async function callerFun() {
  console.log('Caller');
  await testAsync();
  console.log('After waiting');
}

callerFun();

出力:

Caller
Hello from inside the testAsync function
After waiting

関連記事 - JavaScript Promises