JavaScript Promise 非同期処理 Async Function

JavaScript ES6(ECMAScript 2015)で導入された Promise や Async Function(async/await)の基本的な使い方についての覚書です。

作成日:2020年6月17日

参考サイト

同期処理と非同期処理

プログラムの処理は同期処理(sync)と非同期処理(async)に分類することができます。

同期処理は記述されたコードを順番に処理し、1つの処理が終了すると次の処理を実行します。そのため、どれかの処理に時間がかかるとその処理が終了するまでその次の処理に進むことができません。

以下の oneSecMsg() は1秒経過したらメッセージを表示して終了する関数で、この関数を実行すると1秒間処理がブロックされます。

以下を実行すると、「Start」とすぐに表示され、oneSecMsg() により同期的にブロックされ1秒後に「一秒経過」と表示され、その後すぐに「End」と表示されます。

function oneSecMsg(msg) { //1秒経過したらメッセージを表示する関数
  // 開始時刻
  const start = Date.now();
  // 1秒経過するまでループ
  while (true) {
    //現在の時刻と開始時刻の差
    const diff = Date.now() - start;
    if (diff >= 1000) {
      // 1秒経過したらメッセージを表示して終了
      console.log(msg);
      return; 
    }
  }
}

console.log('Start');
oneSecMsg('一秒経過');
console.log('End');

//出力結果
Start
一秒経過
End

非同期処理

非同期処理は記述されたコードを順番に処理しますが、1つの処理が終了するのを待たずに次の処理を実行することができます。

JavaScript の setTimeout() は指定した遅延時間が経過するとコールバック関数を呼び出す非同期処理の関数です。setTimeout() が実行された後、JavaScript はコールバック関数の結果を待たずに次の処理に進むことができます。

以下は setTimeout() に指定したコールバック関数を使って1秒後にメッセージを表示しています。

setTimeout() に指定したコールバック関数は非同期的なタイミングで呼ばれる(ノンブロッキングである)ため、次の行(9行目)の同期的処理は、非同期処理の setTimeout() のコールバック関数よりも先に実行されます(コードの記述順とは異なる順番で実行されます)。

console.log('Start');

//1000ミリ秒後にコールバック関数を実行
setTimeout(() => {
  //コールバック関数
  console.log('一秒後に表示');
}, 1000);

console.log('End'); //上記コールバック関数より先に実行される

//出力結果
Start
End
 一秒後に表示

コールバック関数

以下は画像を先読みする関数 loadimage() の例で、生成した img 要素の src 属性に画像のパスを設定した後に、その画像の高さと幅を取得して出力する記述があります。

function loadimage(src) {
  //img 要素を生成
  let img = document.createElement('img');
  //img 要素の src 属性に引数で指定されたパスを設定
  img.src = src;
  //画像の高さと幅を取得して出力
  console.log(`height:${img.height} / width:${img.width}`);
}

//上記関数を実行
loadimage('images/01.jpg');

//出力結果(高さと幅を取得できていない)
height:0 / width:0

上記を実行すると画像の高さと幅を取得することができません(キャッシュされていれば取得されます)。

これは img.src に画像のパスが指定されて読み込みが開始されますが、Image オブジェクトの読み込み(デコード)処理は非同期的に行われるため、まだ読み込みが完了していない時点で画像の高さと幅の取得が実行されてしまうためです。

この問題を解決するにはイベント(とそのコールバック関数)を使用します。

以下は onload イベントを使って画像がロードされたら画像の高さと幅を出力するコールバック関数を指定する例です。

function loadimage(src) {
  let img = document.createElement('img');
  img.src = src;
  //onload イベントで画像がロードされたらコールバック関数を実行
  img.onload = () => console.log(`height:${img.height} / width:${img.width}`);
}

//上記関数を実行
loadimage('images/01.jpg');

//出力結果(高さと幅を取得できている)
height:1200 / width:1800

以下は上記と同じことですが、第2引数にコールバック関数を受け取るように書き換えた例です。

function loadimage(src, callback) {
  let img = document.createElement('img');
  img.src = src;
  img.onload = () => callback(img);
}
 
//第2引数にコールバック関数を指定して関数を実行
loadimage('images/01.jpg', function(img) {
  console.log(`height:${img.height} / width:${img.width}`);
}); 
  
//コールバック関数をアロー関数で記述した場合の実行例
loadimage('images/01.jpg', (img) => {
  console.log(`height:${img.height} / width:${img.width}`);
}); 

// この場合は引数の括弧やブロックは省略できるので以下のようにも記述できます
loadimage('images/01.jpg', img => console.log(`height:${img.height} / width:${img.width}`));

例外処理(Error-first callbacks)

前述の例はエラー(例外)を考慮していませんが、以下はコールバックを使ってエラー(例外)が発生した場合に対応する例です。

以下の関数 loadimage() は画像の読み込みに成功した場合は callback(null, img) を呼び、エラーが発生した場合は callback(new Error(`Image Not Found "${src}".`)) を呼びます。

画像の読み込みに成功した場合は、コールバック関数の1番目の引数に null、2番目の引数に読み込んだ画像を渡します。

画像の読み込みに失敗した(エラーが発生した)場合は、コールバック関数の1番目の引数に Error オブジェクトが渡されます。

コールバック関数では受け取った1番目の引数の値により処理を分岐します。

エラーが発生した場合は引数 error に Error オブジェクトが渡されるため error は真になるので、console.log(error)が実行されます。

画像の読み込みに成功した場合は error に null が渡されるので console.log(`height:${img.height} / width:${img.width}`) が実行されます。

function loadimage(src, callback) {
  let img = document.createElement('img');
  img.src = src;
  // 画像がロードされた場合(onload イベント)
  // コールバック関数の第1引数に null、第2引数に読み込んだ画像を指定
  img.onload = () => callback(null, img);
  // エラーが発生した場合(onerror イベント)
  // コールバック関数の第1引数に Error オブジェクトを生成して指定
  img.onerror = () => callback(new Error(`Error: Image Not Found "${src}".`));
}

//第2引数にコールバック関数を指定して関数 loadimage() を実行
loadimage('images/01.jpg', (error, img) => {
  // エラーが発生した場合
  // 9行目で引数 error に Error オブジェクトが渡されるので error は true
  if (error) {
    // Error オブジェクトの message プロパティを出力
    console.log(error.message);
  } else {  
    // 画像がロードされた場合
    // 6行目で引数 error に null が渡されるので error は false
    // 画像の高さと幅を出力
    console.log(`height:${img.height} / width:${img.width}`);
  }
});
  
//指定した画像が問題なくロードされた場合の出力例
height:1200 / width:1800

//指定した画像が存在せず、エラーになった場合の出力
Error: Image Not Found "images/02.jpg".

上記は Promise が導入されるまで非同期処理中に発生した例外を扱うパターンとして広く使われているもので Error-first callbacks と呼ばれ、以下のような決まりになっています。

  • 処理失敗時:コールバック関数の1番目の引数にエラーオブジェクトを渡して呼び出す(Error-first)
  • 処理成功時:コールバック関数の1番目の引数には null を渡し、2番目以降の引数に成功時の結果を渡して呼び出す

この決まりにより、コールバック関数では渡される1番目の引数を判定して失敗した場合と成功した場合の処理を分岐して対応することができます。

このパターンは Node.js でもよく使われています(Node.js:Error-first callbacks)。

Promise を使ってみる

前述の非同期処理における例外処理(Error-first callbacks)は単なる慣例であって、仕様ではないためいろいろな書き方ができてしまいます。また、ネストが深くなるとコードの見通しが悪くなり、変更などをする場合のコストが高くなってしまいます。

そのため、JavaScript ES6(ECMAScript 2015)で Promise という非同期処理を扱うための組み込みオブジェクトが導入されました。

Promise はチェインによってフロー制御をし、then メソッドをつなげていくことで、1つの非同期処理が終了したら次の非同期処理というように一連の非同期処理を簡単に書くことができます。

また、catch メソッドを使うことで包括的な例外(エラー)処理ができるようになっています。

以下は前述の画像先読みの例外処理のコードです。

function loadimage(src, callback) {
  let img = document.createElement('img');
  img.src = src;
  // 成功した場合
  img.onload = () => callback(null, img);
  // 失敗した場合
  img.onerror = () => callback(new Error(`Error: Image Not Found "${src}".`));
}

//第2引数にコールバック関数を指定して関数 loadimage() を実行
loadimage('images/01.jpg', (error, img) => {
  if (error) {
    // 非同期処理が失敗したときの処理
    console.log(error.message);
  } else {
    // 非同期処理が成功したときの処理
    console.log(`height:${img.height} / width:${img.width}`);
  }
});

以下は上記を promise を使って書き換えた例です。書き換えた関数 loadimage() はコールバック関数を受け取りません。代わりに promise オブジェクトを生成し返します。

使用する側では loadimage() 関数を実行して promise オブジェクトを生成します。そして then メソッドを使用してそのオブジェクトに対して成功と失敗時の処理をコールバック関数として登録します。

function loadimage(src) {
  return new Promise((resolve, reject) => {
    let img = document.createElement('img');
    img.src = src;
    // 成功(resolve)した場合
    img.onload = () => resolve(img);
    // 失敗(reject)した場合
    img.onerror = () => reject(new Error(`Error: Image Not Found "${src}".`));
  });
}

//promise オブジェクトを生成
let promise = loadimage('images/01.jpg');

//then メソッドを使用
promise.then(
  // 非同期処理が成功したときの処理(コールバック)を登録
  (img) => console.log(`height:${img.height} / width:${img.width}`),
  // 非同期処理が失敗したときの処理(コールバック)を登録
  (error) => console.log(error.message)
);

/*** または then と catch メソッドを使って以下のようにも記述できます。***/
promise.then(
  // 非同期処理が成功したときの処理(コールバック)を登録
  (img) => console.log(`height:${img.height} / width:${img.width}`)
).catch(
  // 非同期処理が失敗したときの処理(コールバック)を登録
  (error) => console.log(error.message)
); 

以下はアロー関数の代わりに関数リテラル(関数式)を使って記述した場合の例です。

function loadimage(src) {
  return new Promise(function(resolve, reject) {
    let img = document.createElement('img');
    img.src = src;
    img.onload = function() {
      resolve(img);
    };
    img.onerror = function() {
      reject( new Error(`Error: Image Not Found "${src}".`) );
    };
  });
}

let promise = loadimage('images/01.jpg');
  
promise.then(
  function(img) {
    console.log(`height:${img.height} / width:${img.width}`);
  },
  function(error) {
    console.log(error.message);
  }
);

//上記を then と catch メソッドを使って記述する場合の例
promise.then(
  function(img) {
    console.log(`height:${img.height} / width:${img.width}`);
  }
).catch(
  function(error) {
    console.log(error.message);
  }
);

Promise インスタンスの生成

Promise は new 演算子で Promise オブジェクトのインスタンスを作成して利用します。

以下は Promise オブジェクトのコンストラクタの構文です。

コンストラクタには executor と呼ばれる関数を渡します。この関数は引数に resolve と reject(またはいずれか)を取ります。

let promise = new Promise( function( resolve, reject ) {
  // executor (関数)の処理(非同期処理を記述)
  // 非同期処理が成功した場合は resolve() を呼び出す
  // 非同期処理が失敗した場合は reject() を呼び出す
});

//アロー関数での記述(上記と同じこと)
let promise = new Promise( (resolve, reject) => {
  ・・・
});

executor(関数)内では処理を行い、その処理結果により以下の関数を呼び出してその状態を反映します。

  • 非同期処理が成功した場合: resolve() 関数 (引数には任意の型の値を指定可能)
  • 非同期処理が失敗した場合: reject() 関数 (引数には Error オブジェクトを指定)

非同期処理が成功した場合のみを扱う場合は resolve() 関数のみを呼び出し、非同期処理が失敗した場合のみを扱う場合は reject() 関数のみを呼び出すこともできます。

executor はそれぞれの処理結果に応じて resolve または reject を1つだけ呼びだす必要があります。2つ以上呼び出しても2つ目以降は無視されます。

  • 成功した場合に対して1つの resolve
  • 失敗した場合に対して1つの reject

また、resolve() や reject() に2つ以上の引数を渡した場合、最初の引数のみが使われ、以降の引数は無視されます。

Promise オブジェクトのインスタンスが生成されると自動的に executor(関数)が呼び出されます。また、コンストラクタが promise を生成する際に、対応する resolve と reject の関数のペアも生成します。

生成された promise インスタンスは内部の(API からは直接操作できない)プロパティを持っています。

promise オブジェクトの状態
state(状態) 説明 result
pending new Promise でインスタンスを生成した時の状態(初期状態) undefined
fulfilled 処理が成功して完了したこと(resolve)を意味し、何らかの値(value)を持っています value
rejected 処理が失敗したこと(reject)を意味し、失敗(エラー)の理由(error)を持っています error

pending 状態の Promise は処理結果により、fulfilled 状態、または rejected 状態のいずれかに変わり、then メソッドによって関連付けられたコールバック関数(ハンドラ)が呼ばれます。

promise オブジェクトの状態は、一度 Pending から fulfilled や rejected に遷移すると、その promise オブジェクトの状態はそれ以降変化することはありません。Event 等とは異なり、promise の状態の変化は最終的なものです。

言い換えると、executor により行われた処理は1つの結果(value)またはエラー(error)のみを持つということになります。

then メソッド

promise オブジェクトには promise の状態(fulfilled/rejected)が遷移した時に呼ばれるコールバック関数を登録するための promise.then() というインスタンスメソッドがあります。

then() は2つの引数として、成功した場合のコールバック(onFulfilled)と失敗した場合のコールバック(onRejected)を取ります。いずれも省略可能で、成功した場合か失敗した場合いずれかのコールバックのみを追加することができます。

promise.then(onFulfilled, onRejected)

以下が構文です(上記の詳細)。

promise.then(
  function(value) { /* 成功した結果を扱うコールバック関数(onFulfilled)の処理 */ },
  function(error) { /* エラーを扱うコールバック関数(onRejected)の処理 */ }
);

//アロー関数を使って記述する場合
promise.then(
  (value) => { /* 成功した結果を扱うコールバック関数(onFulfilled)の処理 */ },
  (error) => { /* エラーを扱うコールバック関数(onRejected)の処理 */ }
);
onFulfilled コールバック(1つ目の引数)
非同期処理が成功/解決(resolve)したときに実行され、その結果(value)を受け取る関数
onRejected コールバック(2つ目の引数)
非同期処理が失敗/拒否(reject)したときに実行され、エラー(error)を受け取る関数

以下は非同期処理が成功した場合の流れです。

  • executor には非同期で実行されるコールバック関数(resolve, reject)が定義されています
  • 非同期の処理が成功すると resolve(value) が実行されます
  • value は promise インスタンスに戻されます
  • promise インスタンスは(非同期に) 関連した then メソッドを実行します
  • value は promise.then() に受け取られて、onFulfilled の実行の入力パラメーターとして渡されます

以下の random_results 関数は Promise のインスタンスを作成して返します。 random_results() は引数に取った数値(num)と生成した乱数(rand)が一致した(成功した)場合は、引数に生成した乱数の値を渡して resolve() を呼び、値が一致しない(失敗した)場合は引数に Error オブジェクトを渡して reject() を呼びます。

resolve() に渡した値は、then メソッドの第1引数のコールバック(onFulfilled:23行目)に渡されます。

reject() に渡した Error オブジェクトは、then メソッドの第2引数のコールバック(onRejected:25行目)に渡されます。

function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数と一致しない)
        // エラーオブジェクトを引数に指定
        reject(new Error(`残念でした。値:${rand}`));
      }
    }, 1000 );
  });
} 
 
//promise オブジェクトを生成
let promise = random_results(4);
  
promise.then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`),
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
);

/*
実行例
大当たり!:4     //成功時
残念でした。値:8  //失敗時
*/

成功した場合のコールバック関数だけを登録

処理が成功した場合だけを扱いたい場合は、then() に1つの引数だけを指定することもできます。

以下は promise インスタンスに対して then メソッドで成功時のコールバック関数だけを登録する例です。

function random_results(num) {
  //Promise のインスタンスを作成して返す(処理が成功した場合だけを扱う例)
  return new Promise((resolve) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } 
    }, 100 );
  });
} 
 
//promise オブジェクトを生成
let promise = random_results(4);
  
promise.then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`),
);
  

失敗した場合のコールバック関数だけを登録

処理が失敗した場合のコールバック関数だけを登録することもできます。 この場合は、then(undefined, onRejected)のように第1引数には undefined を渡す必要があります。

また、catch メソッドを使って catch(onRejected) としても then(undefined, onRejected) と同じことができます。

function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //失敗した場合(乱数と一致しない)
      if (rand !== num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        reject(new Error(`残念でした。値:${rand}`));
      } 
    }, 1000 );
  });
} 
 
//promise オブジェクトを生成
let promise = random_results(4);
  
promise.then( undefined,
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
);


//以下のように catch メソッドを使っても上記の then(undefined, onRejected) と同じこと
promise.catch(
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
);

catch メソッド

promise.catch() は promise オブジェクトが rejected の状態になった時(処理の失敗の場合)に呼ばれる関数を登録するためのインスタンスメソッドです。

また、catch メソッドの実態は then(undefined, onRejected) のシンタックスシュガー(簡単な記述方法)です(失敗した場合のコールバック関数だけを登録)。

then() に1つの引数だけを指定して成功した場合のコールバック関数だけを登録し、失敗した場合のコールバック関数は catch() を使って登録することもできます。

function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数と一致しない)
        // エラーオブジェクトを引数に指定
        reject(new Error(`残念でした。値:${rand}`));
      }
    }, 1000 );
  });
} 
  
//promise オブジェクトを生成
let promise = random_results(4);
  
//成功時のコールバックは then で登録し、失敗時のコールバックを catch で登録
promise.then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`)
).catch(
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
);

関連項目:失敗時の処理 catch メソッド

finally メソッド

promise.finally() は処理の成功時/失敗時のどちらの場合でも呼び出されるコールバック関数を登録するためのインスタンスメソッドで、ECMAScript 2018 で導入されました。

finally() を使えば、処理の成功・失敗に関係なく必要なコードを実行することができます。

以下のような特徴があります。

  • finally() は引数を取りません
  • finally() は次のハンドラに結果(の値)またはエラーを渡します
function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数と一致しない)
        // エラーオブジェクトを引数に指定
        reject(new Error(`残念でした。値:${rand}`));
      }
    }, 1000 );
  });
} 
  
//promise オブジェクトを生成
let promise = random_results(4);
  
promise.then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`)
).catch(
  // 非同期処理が失敗したときのコールバックを catch で登録
  (error) => console.log(error.message)
).finally(
  //成功でも失敗でも呼び出されるコールバック
  ()=> console.log('Finally!')
) 

/*
実行例
//成功時
大当たり!:4     
Finally! 

//失敗時
残念でした。値:8 
Finally!
*/

finally() は次のハンドラに結果あるいはエラーを渡します。以下の例では結果(resolve の引数 rand)が finally から then へと渡されています(26行目)。

function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数と一致しない)
        // エラーオブジェクトを引数に指定
        reject(new Error(`残念でした。値:${rand}`));
      }
    }, 1000 );
  });
} 
  
//promise オブジェクトを生成
let promise = random_results(4);
      
promise.finally(
  //成功でも失敗でも呼び出されるコールバック
  ()=> console.log('Start!')
).then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`)
).catch(
  // 非同期処理が失敗したときのコールバックを catch で登録
  (error) => console.log(error.message)
).finally(
  //成功でも失敗でも呼び出されるコールバック
  ()=> console.log('End!')
)

/*
実行例
//成功時
Start!
大当たり!:4     
End!

//失敗時
Start!
残念でした。値:8 
End!
*/

Promise と JavaScript の例外

Promise ではコンストラクタのコールバック関数(executor)内での処理で例外が発生した(エラーがスローされた)場合、自動的に例外が捕捉(キャッチ)されます。

Promise の処理で例外(エラー)が発生すると、失敗して reject() を呼び出したのと同じように then メソッドの第2引数や catch メソッドで登録した失敗の場合のコールバック関数が呼び出されます。

そしてそれ以降のコンストラクタの処理は実行されません。

また、then や catch のコールバック関数内で発生した例外も自動的に捕捉(キャッチ)されます。

function random_results(num) {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    
    //コンストラクタ内で発生した例外は自動的に捕捉され reject() を呼び出しエラーが渡されます
    throw new Error("例外発生!");
    // 例外が発生すると以降の処理は実行されません
    
    setTimeout(() => {
      //成功した場合(乱数と一致した)
      if (rand === num) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数と一致しない)
        // エラーオブジェクトを引数に指定
        reject(new Error(`残念でした。値:${rand}`));
      }
    }, 1000 );
  });
} 
 
//promise オブジェクトを生成
let promise = random_results(4);
  
promise.then(
  // 非同期処理が成功したときのコールバック
  (result) => console.log(`大当たり!:${result}`),
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
);

/*
実行例
例外発生!
*/

Promise.resolve

Promise.resolve() は fulfilled の状態(resolve() を呼び出した)状態となった Promise インスタンスを返す静的メソッドで、渡された任意の値に即時に解決(resolve)される Promise を作成します。

通常 executor は非同期で何らかの処理を行い、暫く時間が経過した後に resolve(や reject)を呼び出しますが、この場合はすぐに resolve が呼び出されます。

const promise = Promise.resolve(); 

Promise.resolve() は以下のシンタックスシュガー(簡単な記述方法)です。

const promise = new Promise((resolve) => {
  resolve();
});

//または、以下でも同じ
const promise = new Promise(resolve => resolve('hello'));

引数に値を渡すこともできます。

const promise = Promise.resolve('hello'); 

//以下と同じこと
const promise = new Promise((resolve) => {
  resolve('hello');
});

promise.then( value => console.log(value) );  // hello と出力

//引数に値を渡さずに呼び出すと undefined で実行される Promise が作成されます
const promise = Promise.resolve(); 
promise.then( value => console.log(value) );  // undefined と出力

既に fulfilled の状態に遷移した Promise インスタンスの(then メソッドで登録した)コールバック関数の呼び出しは非同期なタイミングで実行されます。

以下を実行すると、同期的な処理が実行された後に、then メソッドのコールバック関数が実行されることが確認できます。

const promise = Promise.resolve('hello');

//then メソッドで登録したコールバック関数の呼び出し
promise.then((val) => {
    console.log(val);  // hello
});

//同期的な処理
console.log('こんにちは');   

//同期的な処理
console.log('ハロー'); 

//出力
/*
こんにちは
ハロー
hello
*/

Promise.reject

Promise.reject() は rejected の状態(reject() を呼び出した)状態となった Promise インスタンスを返す静的メソッドです。

引数には Error オブジェクトを渡します。

const promise = Promise.reject(new Error('Error!'));

Promise.reject() は以下のシンタックスシュガー(簡単な記述方法)です。

const promise = new Promise((resolve, reject) => {
  reject(new Error('Error!'));
});

//または、以下でも同じ
const promise = new Promise((resolve, reject) => reject(new Error('Error!')));

Promise.reject() で作成した rejected の状態の Promise インスタンスに対しても then や catch メソッドでコールバック関数を登録することができます。

const promise = Promise.reject(new Error('Error!'));
 
promise.catch((error) => {
    console.log(error.message); //Error!
});  
  
// then メソッドを使った記述(上記と同じこと)
promise.then( undefined, (error) => {
  console.log(error.message); //Error!
}); 

//error を使わない例
promise.catch(() => {
    console.log('エラー!');  //エラー!
}); 

既に rejected の状態に遷移した Promise インスタンスの(catch や then メソッドで登録した)コールバック関数の呼び出しは、fulfilled の場合と同様、非同期なタイミングで実行されます。

以下を実行すると、同期的な処理が実行された後に、catch メソッドのコールバック関数が実行されることが確認できます。

//catch メソッドで登録したコールバック関数の呼び出し  
Promise.reject(new Error('Error!')).catch((error) => {
    console.log(error.message);  //Error!
});  
 
//同期的な処理
console.log('こんにちは');   
 
//同期的な処理
console.log('ハロー'); 

//出力
/*
こんにちは
ハロー
Error!
*/

Promise チェーン

then メソッドと catch メソッドは常に新しい Promise インスタンスを作成して返します。

そのため、then メソッドの返り値である Promise インスタンスにさらに then メソッドで処理を登録することで、これらをチェーン (連鎖) させることができます。

Promise チェーンでは、Promise が失敗しない(rejected 状態にならない)限り、順番に then メソッドで登録した成功時のコールバック関数が呼び出されます。

また、任意の値(結果)を返すことで、値(結果)を then メソッドのチェーン (連鎖)を通じて渡すことができます。 then メソッドで値が返されるとき、その promise は解決され、次の then メソッドはその値で実行されます。

言い換えると、then メソッドのコールバック関数が返した値は、次の then メソッドのコールバック関数へ引数として渡されます。

以下は then メソッドを使った Promise チェーンの例です。

new Promise(function(resolve, reject) {

  setTimeout(() => resolve('hello'), 1000); //1秒で解決される

}).then(function(result) {

  console.log(result); // hello
  return result + ' hello2'; //結果を(処理して)返す

}).then(function(result) { 

  console.log(result); // hello hello2
  return result + ' hello3'; //結果を(処理して)返す

}).then(function(result) {

  console.log(result); //  hello hello2 hello3

}); 

/*
出力結果
hello
hello hello2
hello hello2 hello3
*/

上記のコードは以下のような流れになります。

  1. 最初の promise は1秒で解決(resolve)されます
  2. 最初の then メソッドが呼ばれ hello が出力されます
  3. 返された値(result + ' hello2')は次の then メソッドへ渡されます
  4. 2つ目の then メソッドが呼ばれ hello hello2 が出力されます
  5. 返された値(result + ' hello3')は次の then メソッドへ渡されます
  6. 3つ目の then メソッドが呼ばれ hello hello2 hello3 が出力されます

promise を返す(非同期処理のチェーン)

通常、then メソッドにより返された値は直ちに次の then メソッドに渡されますが、promise が返された場合はそれ以降の処理はその promise が解決する(処理が完了する)まで中断されます。処理が完了後、promise の結果が次の then メソッドに渡されます。

以下の場合、最初の then メソッドでは3行目の setTimeout により1秒後に hello が出力され、new Promise(...) で promise を返します。1秒後にそれは解決され(処理が完了し)結果(resolve の引数) は2番目の then メソッドに渡されます。そして同じことが繰り返されます。

出力される内容は前述の例と同じですが、以下の例の場合はそれぞれの出力の間に1秒の間隔(遅延)があります。

promise を返すことで非同期処理のチェーンを組み立てることができます。

new Promise(function(resolve, reject) {

  setTimeout(() => resolve('hello'), 1000); //1秒で解決される

}).then(function(result) {

  console.log(result); // hello(1秒後)
  //promise を返す
  return new Promise((resolve, reject) => { 
    setTimeout(() => resolve(result + ' hello2'), 1000);
  });

}).then(function(result) { 

  console.log(result); // hello hello2(さらに1秒後)
  //promise を返す
  return new Promise((resolve, reject) => { 
    setTimeout(() => resolve(result + ' hello3'), 1000);
  });

}).then(function(result) {

  console.log(result); //  hello hello2 hello3(さらに1秒後)

});

/*
出力結果
hello  //開始から1秒後
hello hello2  //開始から2秒後
hello hello2 hello3  //開始から3秒後
*/

失敗時の処理 catch メソッド

処理が成功(resolve)した場合は then メソッドの第1引数で登録した成功時の処理だけが呼び出され、処理が失敗(reject)した場合は then メソッドの第2引数で登録した失敗時の処理または catch メソッドで登録した失敗時の処理だけが呼び出されます。

以下の is_random_even() は乱数を生成して、生成した乱数が偶数の場合は処理が成功(fulfilled)として resolve(rand) で乱数の値を成功時のコールバックに渡し、生成した乱数が奇数の場合は処理が失敗(rejected)とする Promise のインスタンスを作成して返す関数です。

以下は then メソッドで成功時の処理を記述し、catch メソッドで失敗時の処理を記述した非同期処理のチェーンの例です。

生成した乱数が奇数で処理が失敗した(Promise が rejected の状態になった)場合は、最も近い失敗時の処理が呼び出され、間にある成功時の処理はスキップされて実行されません。

function is_random_even() {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数が偶数の場合)
      if (rand%2 === 0) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数が奇数の場合)
        // エラーオブジェクトを引数に指定
        reject(new Error(`偶数ではありません。値:${rand}`));
      }
    }, 1000 );
  });
}   
  
  
//promise オブジェクトを生成
let promise = is_random_even();  
  
promise.then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`1回目の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`2回連続の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`3回連続の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).catch(
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
)

/*出力例*/

//最初に奇数が出た場合(最初で失敗した場合)
//catch で登録した失敗時の処理が呼び出され、成功時の処理は実行されない
//偶数ではありません。値:5

//2回目に奇数が出た場合(2回目で失敗した場合)
//成功時の処理が1回呼び出され、失敗時の処理が呼び出される
//1回目の偶数です。:2
//偶数ではありません。値:3

//3回目に奇数が出た場合(3回目で失敗した場合)
//成功時の処理が2回呼び出され、失敗時の処理が呼び出される
//1回目の偶数です。:4
//2回連続の偶数です。:4
//偶数ではありません。値:3

//3回全てが偶数の場合(全て成功 )
//成功時の処理が3回呼び出され、失敗時の処理は呼び出されない
//1回目の偶数です。:6
//2回連続の偶数です。:4
//3回連続の偶数です。:4

then メソッドや catch メソッドのコールバック関数内で発生した例外も自動的にキャッチされ、then メソッドや catch メソッドは rejected な状態の Promise インスタンスを返します。

そのため、例外が発生する場合も最も近くの失敗時の処理(catch または then の第2引数に登録したコールバック)が呼び出されます。

以下の場合、最初の then メソッドにより hello と出力されますが、14行目で例外が発生しているので、その後の処理はスキップされ、catch メソッドに登録された失敗時の処理が呼び出されます。

new Promise((resolve, reject)  => {

  setTimeout(() => resolve('hello'), 1000);

}).then((result) => {
  
  console.log(result);  //hello
  return new Promise((resolve, reject) => { 
    setTimeout(() => resolve(result + ' hello2'), 1000);
  });

}).then((result) => { 

  throw new Error("例外発生!");  //例外を発生させる
  console.log(result); 
  return new Promise((resolve, reject) => { 
    setTimeout(() => resolve(result + ' hello3'), 1000);
  });

}).then((result) => console.log(result)

).catch(
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
); 

/*
//出力
hello
例外発生! //console.log(error.message)
*/

Promise.all

Promise.all を使うと複数の Promise を使った非同期処理を同時(並列)に実行し、全ての Promise の処理が完了した時点で then メソッドのコールバック関数を呼び出します。

複数の Promise を使った非同期処理を1つの Promise として扱うことができます。以下が構文です。

let promise = Promise.all([promiseInstance, ... ]);

Promise.all メソッドは Promise インスタンスの配列を受け取り、新しい Promise インスタンスを返します。 返り値の新しい Promise インスタンスは、配列の全ての Promise インスタンスの処理が解決(resolve)した場合に fulfilled の状態になり、もし1つでも処理が失敗(reject)した場合は、返り値の Promise インスタンスも rejected の状態になります。

then メソッドで登録したコールバック関数が受け取る引数は全ての Promise インスタンスの結果をまとめた配列で、その配列の要素は Promise.all メソッドに渡した配列の要素(Promise インスタンス)の順番になります。

1つずつ順番に処理を実行したい場合は、Promise チェーンを利用します。

以下の場合、Promise.all は全ての処理が完了する2000ミリ秒後に解決し、then メソッドは全ての処理の結果の配列を受け取ります。

受け取る配列の要素の順序は Promise.all メソッドに渡した配列の順番になっています。以下の例では1つ目の promise が解決まで最も時間がかかりますが、結果の配列の中では1つ目になります。

function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}
  
const promise1 = delayed_action(1, 2000);
const promise2 = delayed_action(2, 500);
const promise3 = delayed_action(3, 1000);

Promise.all([promise1, promise2, promise3]).then(response => console.log(response)); 

/* 出力
["1つ目の処理(2000 ミリ秒)", "2つ目の処理(500 ミリ秒)", "3つ目の処理(1000 ミリ秒)"]
*/
  
// forEach を使って出力
Promise.all([promise1, promise2, promise3]).then(responses => responses.forEach(
  response => console.log(response)
));

/* 出力
1つ目の処理(2000 ミリ秒)
2つ目の処理(500 ミリ秒)
3つ目の処理(1000 ミリ秒)
*/

いずれかの Promise インスタンスが reject された場合、Promise.all は即座に rejected になります。

以下の場合、追加した2つ目のインスタンスが3秒後に reject され、即座に Promise.all の reject に繋がり catch メソッドが実行されます。 reject されたエラーは Promise.all 全体の結果になります。

function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}
  
const promise1 = delayed_action(1, 2000);
const promise2 = delayed_action(2, 500);
const promise3 = delayed_action(3, 1000);
  
Promise.all([
  promise1, 
  //rejected になる Promise インスタンスを追加
  new Promise((resolve, reject) => 
        setTimeout(() => 
          reject(new Error('エラー発生!')), 3000)
       ),
  
  promise2, 
  promise3
]).then(responses => responses.forEach(
  response => console.log(response)
)).catch(
  // 失敗したとき(rejected)のコールバック
  (error) => console.log(error.message)
); 

/* 出力
エラー発生!
*/

Promise.race

Promise.race は Promise.all と同様に Promise インスタンスの配列を受け取りますが、いずれか1つの Promise インスタンスの処理が完了し状態が確定(Settle ※)した時点で、その実行結果(またはエラー)を1つだけ取得します。

(※)Promise インスタンスの状態は生成時は Pending で、その後 fulfilled(成功した場合)または rejected(失敗した場合)へ変化し、それ以降状態は変化しません。 このため fulfilled または rejected の状態であることを Settled と呼びます。

以下が構文です。

let promise = Promise.race([promiseInstance, ... ]);

最初に完了した処理の結果またはエラーが Promise.race 全体の結果になります。それ以外の結果やエラーは無視されます。then メソッドのコールバック関数は一度しか呼び出されません。

以下の場合、Promise.race は最初の処理が完了する500ミリ秒後に確定(Settle)し、then メソッドはその処理の結果を受け取ります。

function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}
  
const promise1 = delayed_action(1, 2000);
const promise2 = delayed_action(2, 500);
const promise3 = delayed_action(3, 1000);
  
Promise.race([promise1, promise2, promise3]).then(value => console.log(value)); 

/* 出力
2つ目の処理(500 ミリ秒)
*/

以下は、配列に追加したエラーが発生する処理が最初に確定(Settle)するので、全体の結果はエラーとなり catch メソッドのコールバックが呼ばれます。

function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}
  
const promise1 = delayed_action(1, 2000);
const promise2 = delayed_action(2, 500);
const promise3 = delayed_action(3, 1000);
  
/*Promise.race([promise1, promise2, promise3]).then(value => console.log(value)); */
  
Promise.race([
  promise1, 
  
  //処理時間が一番短く且つエラーになる Promise インスタンスを追加
  new Promise((resolve, reject) => 
        setTimeout(() => 
          reject(new Error('エラー発生!')), 10)
       ),
  
  promise2, 
  promise3
]).then( value => 
  console.log(value)
).catch( error => 
  // 失敗したときのコールバック
  console.log(error.message)
); 

/* 出力
エラー発生!
*/

Async Function

ECMAScript 2017 で導入された Async Function は非同期処理を扱う(promise を利用する)関数を定義する構文で、より簡潔に効率よく記述することができます。

Async Function は通常の関数とは異なり、async を関数の前に付けることで、その関数は Promise オブジェクトを返すようになります。

以下が構文です。

async function 関数名(引数) {
  処理
}

以下のように async キーワードを関数の定義の前に置くことで Async Function を定義できます。

async を付けることでその関数は常に promise を返すため、結果を取得するには then メソッドが使えます。

以下の Async Function は結果の値 'hello' を持つ解決された promise を返します。結果は then メソッドで確認できます。

//async キーワードを付けて Async Function を定義
async function asyncFunc() {
  //返り値が promise でない場合は自動的に promise にラップされる
  return 'hello';  
}
 
//Async Function は Promise オブジェクトを返すので then メソッドが使える
asyncFunc().then(value => {
  console.log(value); // hello
}); 

Async Function の定義の中で return した値(返り値)が Promise オブジェクトでない場合、JavaScript は自動的にその値を持つ解決された Promise にラップします。

上記の Async Function の定義は以下のように記述したのと同じ意味になります。

//Promise.resolve メソッドで記述
async function asyncFunc() {
  return Promise.resolve('hello'); 
}

//Promise オブジェクトのコンストラクタを使った以下でも同じ
async function asyncFunc() {
  return new Promise(resolve => resolve('hello'));
}

Async Function の定義の中で返り値が Promise オブジェクトの場合はそのまま返します。

//Promise オブジェクトを生成する関数
function delayed_hello(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${ms/1000}秒後の Hello!`);
    }, ms)
  })
}  
  
async function asyncFunc() {
  //Promise オブジェクトを返す
  return delayed_hello(1000);
} 
  
asyncFunc().then(value => {
  console.log(value); // 1秒後の Hello!
}); 

返り値が rejected な Promise オブジェクトの場合でも rejected な Promise オブジェクトをそのまま返します。

結果(エラー)を取得するには catch メソッドまたは then メソッドの第2引数を使います。

async function asyncFunc() {
  //rejected な Promise オブジェクトを返す
   return Promise.reject(new Error('Error!'));
} 

//catch メソッド
asyncFunc().catch( error => console.log(error.message));  //Error!
 
//then メソッドの第2引数を使用
asyncFunc().then( undefined,
  error => console.log(error.message)   //Error!
);

Async Function の定義の中で何も値を返さない場合は undefined を返すのと同じことになります。

async function asyncFunc() {
  //何も値を返さない → return Promise.resolve(undefined); と同じ
  const hello = 'hello';
} 
  
asyncFunc().then(value => {
  console.log(value); // undefined
}); 

Async Function の定義の中で例外が発生した場合は、その結果(エラー)を持つ rejected な Promise オブジェクトを返します。

結果(エラー)を取得するには catch メソッドまたは then メソッドの第2引数を使います。

async function asyncFunc() {
  //例外が発生
  throw new Error("例外発生!");
  //例外が発生すると以降の処理は実行されません
} 
  
asyncFunc().catch( error => console.log(error.message)); //例外発生!
  
asyncFunc().then( undefined,
  error => console.log(error.message) //例外発生!
);

await

await は Async Function の関数の中でのみ動作するキーワード(式)で、指定した非同期関数の実行を一時停止し、Promise の解決を待ちます。

そして Promise の処理が完了すると解決された値を返し(変数へ代入し)、Async Function 内の処理を再開(次の行の処理を開始)します。

以下が await の構文です。

  • expression:解決を待つ Promise または何らかの値
  • rv:解決された promise の値(結果)。expression が Promise ではない場合はその値自体を返す。
[rv] = await expression;

await は Promise インスタンスの結果を返します。Promise が fulfilled となった場合は解決された値が返され、rejected となった場合は undefined が返され、理由となった値(エラーの内容)をスローします。

以下は1秒で解決する promise の例です。

関数の実行は await が指定されている行で一時停止し、promise が確定したときに再開します。変数 result には Promise インスタンスの結果の値が入ります。そのため、asyncFunc() の実行により1秒後に「1秒後の Hello!」と出力されます。

//非同期処理の関数(指定したミリ秒後に x秒後の Hello! と表示) 
function delayed_hello(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${ms/1000}秒後の Hello!`);
    }, ms)
  })
}  

//Async Function
async function asyncFunc() {

  // await を指定: delayed_hello() の非同期処理が完了するまで待つ
  // result には Promise インスタンスの結果の値が入る
  let result = await delayed_hello(1000) ;
  
  // 以下の行は delayed_hello() の非同期処理が完了されるまで実行されない
  console.log(result);
  
}  
  
asyncFunc();  //1秒後の Hello!

上記と同じことを then メソッドを使って書くと以下のようなコードになります。 await を使うとコールバック関数を使わずに記述できます。

//非同期処理の関数(指定したミリ秒後に x秒後の Hello! と表示) 
function delayed_hello(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${ms/1000}秒後の Hello!`);
    }, ms)
  })
}  
 
//通常の関数
function regFunc() {

  //Promise インスタンスを変数に代入
  let result = delayed_hello(1000) ;

  //then メソッドのコールバックを使用
  result.then(value => console.log(value));
  
}  
  
regFunc();  //1秒後の Hello!

await を指定した Promise が rejected となった場合は undefined が返され、理由となった値(エラーの内容)をスローします。また、そのエラーの値は try...catch 構文でキャッチすることができます。

async function asyncErrorFunc() {
  
  try {
    // rejected となる Promise
    let result = await Promise.reject('Rejected Promise!!');
  } catch (e) {
    console.log(e); // Rejected Promise!!(e:エラーの内容)
  }
}

//try...catch 構文により以下がコンソールに出力される
//Rejected Promise!! 

//try...catch でエラーは捕捉されているので以下は実行されない
asyncErrorFunc().catch(error => {
  console.log(error);
});

await を指定した Promise が rejected となった場合、その Async Function が rejected な Promise を返すので Promise の catch メソッドでもエラーハンドリングを行えます。

async function asyncErrorFunc() {
          
  // rejected となる Promise
  let result = await Promise.reject('Rejected Promise!!');
  console.log(result);

}
  
// catch メソッドでエラー(の値)を捕捉
asyncErrorFunc().catch(error => {
  console.log(error);  //Rejected Promise!! と出力
  console.log(error.message); // ※ undefined と出力
});

Promise チェーンを await で書き換え

Promise チェーンで記述した非同期処理を Async Function と await を使って書き換える例です。

以下は Promise チェーンで記述した非同期処理の例です。

delayed_hello() は指定した秒数が経過したら「x秒後の Hello!」と表示する Promise を作成する非同期処理の関数です。

function delayed_hello(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${ms/1000}秒後の Hello!`);
    }, ms)
  })
}  
 
//Promise チェーンでの記述  
delayed_hello(300).then(
  // 非同期処理が成功したときのコールバック
  result => {
    //結果(値)を出力
    console.log(result);
    // Promise を返す
    return delayed_hello(500); 
  }
).then(
  result => {
    console.log(result);
    return delayed_hello(700); 
  }
).then(
  result => {
    console.log(result);
    return delayed_hello(1000); 
  }
).then(
  result => {
    console.log(result);
  }
)

/* 出力
0.3秒後の Hello!
0.5秒後の Hello!
0.7秒後の Hello!
1秒後の Hello!
*/

上記を Async Function と await を使って記述すると以下のようになります。await を使ってそれぞれの処理が完了するまで待ち、その結果を出力しています。

promise.then で記述するより簡潔に記述することができます。

// Async Function と await を使って記述
async function asyncFunc() {
  //Promise の処理をすべて await を付けて実行
  console.log(await delayed_hello(300));
  console.log(await delayed_hello(500));
  console.log(await delayed_hello(700));
  console.log(await delayed_hello(1000));
}  
  
asyncFunc(); 

/* 出力
0.3秒後の Hello!
0.5秒後の Hello!
0.7秒後の Hello!
1秒後の Hello!
*/

以下は乱数を生成して、生成した乱数が偶数の場合は乱数の値を成功時のコールバックに渡し、生成した乱数が奇数の場合は処理が失敗(rejected)とする Promise のインスタンスを作成して返す関数を使った Promise チェーンの例です。

この場合は、処理が失敗(rejected)となる可能性があるのでエラー処理が必要になります。

//生成した乱数の値により成功・失敗のコールバック関数を呼び出す関数
function is_random_even() {
  //Promise のインスタンスを作成して返す
  return new Promise((resolve, reject) => {
    let rand = Math.floor( Math.random() * 10 );
    setTimeout(() => {
      //成功した場合(乱数が偶数の場合)
      if (rand%2 === 0) {
        // 引数に指定した値を成功したときのコールバックに渡す
        resolve(rand);
      } else { //失敗した場合(乱数が奇数の場合)
        // エラーオブジェクトを引数に指定
        reject(new Error(`偶数ではありません。値:${rand}`));
      }
    }, 1000 );
  });
}    

//Promise チェーンでの記述 
is_random_even().then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`1回目の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`2回連続の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).then(
  // 非同期処理が成功したときのコールバック
  result => {
    console.log(`3回連続の偶数です。:${result}`);
    // Promise を返す
    return is_random_even(); 
  }
).catch(
  // 非同期処理が失敗したときのコールバック
  (error) => console.log(error.message)
) 

上記を Async Function と await 及び try...catch 構文を使って記述すると以下のようになります。

//Async Function と await 及び try...catch 構文を使って記述
async function asyncFunc() {
  try {
    let result = await is_random_even();
    console.log(`1回目の偶数です。:${result}`);

    result = await is_random_even();
    console.log(`2回連続の偶数です。:${result}`);

    result = await is_random_even();
    console.log(`3回連続の偶数です。:${result}`);
  }catch (e) {
    console.log(e); 
  }
} 
  
asyncFunc();

try...catch 構文の代わりに Promise の catch メソッドを使って記述すると以下のようになります。

//Async Function と await 及び catch メソッドを使って記述
async function asyncFunc() {
  
  let result = await is_random_even();
  console.log(`1回目の偶数です。:${result}`);

  result = await is_random_even();
  console.log(`2回連続の偶数です。:${result}`);

  result = await is_random_even();
  console.log(`3回連続の偶数です。:${result}`);

}  

//catch メソッドでエラーを捕捉
asyncFunc().catch(err => {
  console.log(err);  
});

Promise.all と await

Promise.all を使うと複数の非同期処理を1つの Promise インスタンスにまとめることで同時に取得することができます。

await も Promise.all メソッドと組み合わせて使うことができます。

以下は Promise.all を使って複数の Promise を使った非同期処理を並列に実行し、全ての処理が完了した時点で then メソッドのコールバック関数で forEach を使って結果を出力する例です。

//指定したミリ秒後にメッセージを表示する非同期処理の Promise インスタンスを作成
function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}
  
const promise1 = delayed_action(1, 2000);
const promise2 = delayed_action(2, 500);
const promise3 = delayed_action(3, 1000);
 
//Promise インスタンスの配列を渡して実行し then メソッドのコールバック関数で出力
Promise.all([promise1, promise2, promise3]).then(responses => responses.forEach(
  response => console.log(response)
)); 

/* 出力(同時に出力される)
1つ目の処理(2000 ミリ秒)
2つ目の処理(500 ミリ秒)
3つ目の処理(1000 ミリ秒)
*/

以下は Promise.all と Async Function を組み合わせて、同時に処理結果を取得する関数 delayed_action_all() の例です。

await は Async Function の中でしか使用できません。

//指定したミリ秒後にメッセージを表示する非同期処理の Promise インスタンスを作成
function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}

//Async Function 
async function  delayed_action_all(params) {
  //delayed_action にパラメータを渡して実行し Promise インスタンスの配列を取得 
  const promises = params.map(function(sec, index) {
    return delayed_action(index + 1, sec);
  });
  
  //Promise.all でラップして await させて非同期処理の結果を配列として取得
  const responses = await Promise.all(promises);
  
  //取得した結果の配列を forEach で出力
  responses.forEach(
    response => console.log(response)
  )

}

//delayed_action_all に渡すパラメータの配列
const params = [2000, 500, 1000]; 

//delayed_action_all を実行
delayed_action_all(params);

/* 出力(約2秒後に同時に出力される)
1つ目の処理(2000 ミリ秒)
2つ目の処理(500 ミリ秒)
3つ目の処理(1000 ミリ秒)
*/

いずれかの Promise インスタンスが reject された(失敗した)場合、Promise.all は即座に rejected になり、エラーは失敗した promise から Promise.all に伝播します。

以下は前述の例に reject される Promise インスタンスを追加してエラーを発生させる例です。エラーは delayed_action_all() 実行時に Promise の catch メソッドで捕捉しています。

追加した最後のインスタンスが0.3秒後に reject されると即座に Promise.all の reject に繋がり catch メソッドが実行されます。

function delayed_action(num, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${num}つ目の処理(${ms} ミリ秒)`);
    }, ms)
  })
}

async function  delayed_action_all(params) {
  const promises = params.map(function(sec, index) {
    return delayed_action(index + 1, sec);
  });
  
  //reject される Promise インスタンスを作成
  const error_promise = new Promise((resolve, reject) => 
        setTimeout(() => 
          reject(new Error('エラー発生!')), 300)
       );
  
  //上記のインスタンスを Promise.all に渡す配列に追加
  promises.push(error_promise);
        
  const responses = await Promise.all(promises);
  
  responses.forEach(
    response => console.log(response)
  )
}

const params = [2000, 500, 1000]; 
  
delayed_action_all(params).catch(
  // 失敗したとき(rejected)のコールバック
  (error) => console.log(error.message)
); 

/* 
出力(約0.3秒後に出力)
エラー発生!
*/