JavaScript Promise の使い方 / 非同期処理 Async Function/ Fetch

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

fetch() メソッドの使い方を追加しました。2020年7月22日

作成日: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秒後に出力)
エラー発生!
*/

Fetch

Fetch API は HTTP 通信を行ってリソースを取得するための API で XMLHttpRequest(XHR)に代わる API として作られたものです。

IE や一部のブラウザは対応していません(caniuse)。

英語の fetch には「取得する(取ってくる)」というような意味があります。

fetch() メソッドは非同期通信でリクエストを発行し、そのレスポンスを取得する関数です。fetch() メソッドを使うと非同期通信の処理を簡潔に記述することができます。

以下が fetch() メソッドの基本的な書式です。

let promise = fetch(input, [options])

fetch() メソッドを呼び出すとリクエストが発行されます。そして fetch() メソッドは Response オブジェクトを結果に持つ Promise オブジェクトを返します。

input
fetch したいリソースの定義(以下のいずれか)
options
リクエストに適用したいオプション
options(一部抜粋)
オプション 説明
method リクエストするメソッド、GET、POST など。デフォルトは GET。
headers リクエストに追加したいヘッダー
body リクエストに追加したいボディ(method が GET や HEAD の場合使用できません)
mode リクエストで使いたいモード(cors、no-cors, or same-origin)
credentials request に使用したい秘密情報
cache 使用したいリクエストのキャッシュのモード
redirect リダイレクトをどう処理するかについての値
返り値
解決時に Response オブジェクトを取得できる Promise オブジェクト

以下は取得したいファイル(file.txt)に対して fetch() メソッド で HTTP リクエストを行っています(レスポンスを受け取る処理の記述はありません)。

//HTTP リクエストを発行して返り値を変数に代入
let promise = fetch('file.txt');

// 返り値をコンソールに出力
console.log(promise);

//コンソールの出力
Promise {<pending>}  // Promise オブジェクト
__proto__: Promise
[[PromiseStatus]]: "fulfilled"  //状態は正常終了(成功)
[[PromiseValue]]: Response  //結果の値は Response オブジェクト

レスポンスの取得

fetch() の返り値はリクエストのレスポンスを表す Response オブジェクトの Promise なので、レスポンス(レスポンスボディ)の取得は通常2段階のプロセスになります。

返り値の Response オブジェクトの Promise が返ってきた時点では、ステータスコードやヘッダの情報はその Response オブジェクトから取得できますが、本文(レスポンスボディ)を取得する非同期処理は完了していません。

レスポンスの本文(レスポンスボディ)を取得するには追加のメソッド呼び出しが必要です。

正常にリクエストが行われた場合 Promise は Response オブジェクトで resolve します。言い換えると、then() メソッドの最初のパラメータの関数(onFulfilled)の引数に Response オブジェクトが渡され、その Response オブジェクトを使って何らかの処理をすることができます。

この Response オブジェクトではそのプロパティを使って HTTP ステータスをチェックしたり、ヘッダをチェックすることができますが、レスポンスボディ(本文)は持っていません。

fetch('file.txt')
  //onFulfilled の 引数に Response オブジェクトが渡される
  .then(response => {
    // プロパティ status でHTTPステータスコードを出力
    console.log(response.status);  //200
});
Response オブジェクトのプロパティ(抜粋)
プロパティ 説明
headers レスポンスに関連した Headers オブジェクト
ok レスポンスが成功(200-299 の範囲のステータス)したか否かを通知する boolean 値
redirected レスポンスがリダイレクトの結果であるかどうかを示します
status HTTPステータスコード(例:200 成功)
statusText ステータスコードに対応したステータスメッセージ(例:200 なら OK)
type レスポンスのタイプ(例:basic,cors)
url レスポンスのURL

本文の取得

レスポンスボディ(本文)は Response オブジェクトのメソッドを使って取得する必要があります。

例えば、以下のように本文を text() メソッドでテキストとして取得して return することで次の then() メソッドに渡すことができます。

fetch('file.txt')
  //引数に Response オブジェクトが渡される
  .then((response) => {
    //text() メソッドでレスポンスボディを取得してテキストとして返す
    return response.text();
  })
  .then((text) => {
    //引数で受け取ったレスポンスボディをコンソールに出力
    console.log(text);
});

レスポンスボディはそのタイプによって以下のいずれかのメソッドを使って取得することができます。

Response オブジェクトのメソッド(抜粋)
メソッド 説明
arrayBuffer() ArrayBuffer (純粋なバイナリデータ) として値を返す
blob() Blob (型付きのバイナリデータ) として値を返す
formData() FormData オブジェクト(multipart/form-data の MIME Type) として値を返す
json() JSON オブジェクトとして値を返す
text() テキストを値として返す

上記のメソッドはいずれも promise を返すので、次の then() メソッドにチェーンできます。

以下は JSON オブジェクトとしてパースしてレスポンスボディを取得する例です。

以下の例では catch() メソッドを使ったエラー処理を追加しています。但し、404 や 500 のような HTTP エラーは成功とみなされて reject されないので注意が必要です。

fetch('../../sample/package.json')
  .then((response) => {
    //json() メソッドでレスポンスボディを取得してJSONとして返す
    return response.json();
  })
  .then((json) => {
    //引数で受け取った JSON をコンソールに出力
    console.log(json);
  })
  //エラーの場合の処理
  .catch((error) => {
    console.log(error);
});

上記の場合、処理がそれぞれ1行なので以下のように記述することができます。

fetch('../../sample/package.json')
  .then((response) => response.json())
  .then((json) => console.log(json))
  .catch((error) => console.log(error));

本文のパース方法は1つだけ

レスポンスボディを取得する(パースする)メソッドは1つだけ選ぶことができます。

例えば response.json() でレスポンスボディを JSON で取得後、response.text() を実行しても、本文のコンテンツは既に処理されているため動作しません。

以下のように再度 fetch() を実行して JSON とテキストを取得することはできます。

fetch('../../sample/package.json')
  .then((response) => {
    return response.json();
  })
  .then((json) => {
    console.log(json);
    //再度 fetch() を実行
    return fetch('../../sample/package.json');
  })
  .then((response) => {
    return response.text();
  })
  .then((text) => {
    console.log(text);
  })
  .catch((error) => {
    console.log(error);
});

HTTP エラー時に失敗にする

Promise は fetch() が HTTP リクエストを発行できなかった場合(ネットワークの問題やそのようなサイトがない場合など)は reject しますが、404 や 500 のような HTTP エラーは成功とみなされます。

404 や 500 を失敗にしたい場合は、Response オブジェクトの ok プロパティを使って以下のように記述できます。

fetch('file.txt')
.then((response) => {
  // ステータスが ok(ステータスコードが200-299の範囲)ならば
  if(response.ok) { 
    // レスポンスボディを取得してテキストとして返す
    return response.text(); 
  } else {
    //ok でなければ例外を発生させ status をエラーメッセージに含めて表示
    throw new Error(`Request Failed: ${response.status}`);
  }
})
.then((text) => console.log(text))
.catch((error) => console.log(error));
async/await

Async Functionawait を使っても同様にレスポンスを取得することができます。

await は Async Function の関数の中でのみ動作し、Promise の解決を待ち Promise の処理が完了すると解決された値を返します。

async function fetchMyText() {
  //Response オブジェクトを取得
  const response = await fetch('file.txt');
  //レスポンスボディを取得
  const myText = await response.text();
  //取得したレスポンスボディをコンソールに出力
  console.log(myText);
}

//実行
fetchMyText();  

即時関数として実行するには以下のように記述できます。

(async function() {
  const response = await fetch('file.txt');
  const myText = await response.text();
  console.log(myText);
})();

以下はアロー関数を使って書き換えた例です。

const fetchMyText = async () => {
  const response = await fetch('file.txt');
  const myText = await response.text();
  console.log(myText);
};

//実行
fetchMyText();

即時関数として実行するには以下のように記述できます。

(async () => {
  const response = await fetch('file.txt');
  const myText = await response.text();
  console.log(myText);
})();

エラー処理

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

以下は try...catch 構文でエラーを捕捉する例です。

(async () => {
  try {
    const response = await fetch('file.txt');
    const myText = await response.text();
    console.log(myText);
  } catch(e) {
    console.log(e);
  }
})();

但し、404 のような HTTP エラーは成功とみなされて reject されないので、HTTP エラー時に失敗にしたい場合は、Response オブジェクトの ok プロパティを使って以下のように記述できます。

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

(async function() {
  const response = await fetch('file.txt');
  if(response.ok) { 
    //ステータスが ok(ステータスコードが200-299の範囲)ならば
    const myText = await response.text();
    console.log(myText);
  } else {
    //ステータスが ok でなければ例外を発生させ rejected に
    throw new Error(`Request Failed: ${response.status}`);
  }
})().catch((error) => {
    console.log(error);
});
ヘッダの取得

Response オブジェクトのプロパティ headers はレスポンスに関連した Headers オブジェクトで、HTTP レスポンスヘッダの内容が格納されています。

Headers オブジェクトのメソッド get を使って個々のヘッダを指定して取り出したり、for of 文で全てのヘッダを取り出すことができます。

fetch('http://localhost/samples/sample_01.html')
.then((response) => {
  // ステータスが ok ならば
  if(response.ok) { 
    // ヘッダ(Content-Type)を指定して取得
    console.log('Content-Type: ' + response.headers.get('Content-Type')); 
    // etag
    console.log('etag: ' + response.headers.get('etag')); 
    // すべてのヘッダを取得
    for (let [key, value] of response.headers) {
      console.log(`${key} = ${value}`);
    }
  } else {
    throw new Error(`Request Failed: ${response.status}`);
  }
})
.catch((error) => console.log(error));

/* 出力例
Content-Type: text/html
etag: "135fd99-1b4b6-5aafb1c5e2b40"
//以下は for of 文での取得結果
accept-ranges = bytes
content-length = 111798
content-type = text/html
date = Tue, 21 Jul 2020 22:27:34 GMT
etag = "135fd99-1b4b6-5aafb1c5e2b40"
last-modified = Tue, 21 Jul 2020 22:26:13 GMT
server = Apache/2.2.34 (Unix) mod_wsgi/3.5 Python/2.7.13 PHP/7.4.1 mod_ssl/2.2.34 OpenSSL/1.0.2o DAV/2 mod_fastcgi/mod_fastcgi-SNAP-0910052141 mod_perl/2.0.11 Perl/v5.24.0*/

以下は上記を await を使って記述した例です。

(async () => {
  try {
    let response = await fetch('http://localhost/samples/sample_01.html');
    console.log('Content-Type: ' + response.headers.get('Content-Type'));
    console.log('etag: ' + response.headers.get('etag')); 
    for (let [key, value] of response.headers) {
      console.log(`${key} = ${value}`);
    }
  } catch(e) {
    console.log(e);
  }
})();
POST メソッド

POST メソッドを使ったリクエストを行うには fetch() メソッドのオプション(options)を利用します。

以下はボタンをクリックすると入力された Name と ID の値を fetch() メソッドを使って PHP ファイル(fetch_post_test.php)に POST 送信して、そのレスポンス(PHP ファイルでの処理結果の出力)を取得して id="response" の p 要素に出力する例です。

オプションの method に POST を指定(17行目)し、body にアップロードしたいデータ(リクエストボディ)を指定(18行目)します。

POST するフォームのデータは FormData オブジェクトで生成しています。

リクエストボディ(body)が文字列の場合、Content-Type にはデフォルトでは text/plain が設定されます。

<body> 
  <form id="myForm">
    Name : <input type="text" name="name" value="">
    ID : <input type="text" name="id" value="">
    <input id="button" type="button" value="Send">
  </form>
  <p id="response"></p> 
</body>

<script>
document.getElementById('button').addEventListener('click', function() {
  
  // POST するフォームのデータを FormData オブジェクトで生成
  let data = new FormData(document.getElementById('myForm'));

  fetch('fetch_post_test.php', {
    method: 'POST',  //POST メソッド
    body: data,  //リクエストボディ
  })
  .then(function (response) {
    if(response.ok) { 
      // ステータスが ok ならばレスポンスボディを取得
      return response.text(); 
    } else {
      //ok でなければ(404エラーなどの場合)例外を発生
      throw new Error(`失敗(例外発生): ${response.status}`);
    }
  })
  .then(function (text) {
    // PHPファイルから取得したレスポンスボディを id="response" の p 要素に出力
    document.getElementById('response').textContent = text;
  })
  .catch(function (error) {
    // エラーの場合はエラーを id="response" の p 要素に出力
    document.getElementById('response').textContent = error;
  })
})
</script>

以下がリクエスト先の PHP ファイルです。この場合、$_POST['name'] と $_POST['id'] で受け取った値をエスケープ処理して echo で出力しています。

fetch() メソッドが取得するレスポンスボディは出力された値 "Name: {$name} / ID: {$id}" になります。

fetch_post_test.php
<?php
//受け取った値をエスケープ処理
$name = htmlspecialchars($_POST['name'], ENT_QUOTES, 'UTF-8');
$id = htmlspecialchars($_POST['id'], ENT_QUOTES, 'UTF-8');

//echo で出力(レスポンスボディとして取得される)
echo "Name: {$name} / ID: {$id}";

FormData

FormData はフォームのデータを保存したり送信したりするためのオブジェクトで、fetch() メソッドなどで本体(リクエストボディ)として指定することができ、エンコードされて Content-Type: form/multipart で送信されます。

FormData オブジェクトは new キーワードでインスタンスを作成し、append() や set() メソッドでキーと値を追加することができます。set() メソッドは同じキーのエントリーが既に存在していたら、そのエントリーを新しい値で上書きします。

//FormData オブジェクトのインスタンスを生成
let data = new FormData();
            
//新しいキーと値をセット          
data.set("キー", "値");

//新しいキーと値を追加
data.append("キー", "値");

または、前述の例のように <form> 要素から FormData オブジェクトを作成することもできます。

<form id="myForm">
  Name : <input type="text" name="name" value="">
  ID : <input type="text" name="id" value="">
  <input id="button" type="button" value="Send">
</form>
//<form> 要素から FormData オブジェクトを生成
let data = new FormData(document.getElementById('myForm'));

FormData オブジェクトの内容(中身)は、for of 文を使って以下のように確認できます。item[0] にはキーが、item[1] には値が入ります。

let data = new FormData(document.getElementById('myForm'));

//data(FormData オブジェクトの中身)をコンソールへ出力
for (item of data) {
  console.log(`${item[0]}: ${item[1]}`);
}

/*出力例
name: Foo
id: 001
*/

FormData の entries() メソッドを使って以下のように記述しても同じ(等価)です。

for (item of data.entries()) {
  console.log(`${item[0]}: ${item[1]}`);
}

JSON を POST

本文(body)が文字列の場合は Content-Type にはデフォルトで text/plain が設定されます。

JSON データを POST する場合には headers オプションで Content-Type に application/json を設定します。

//JSON データ(JSON 形式のオブジェクト)
let data = {
  name: 'Foo',
  id: '001'
};

fetch('fetch_post_test2.php', {
  method: 'POST',
  //送信する JSON データをシリアライズしてリクエストボディに設定
  body: JSON.stringify(data),
  //headers オプションで application/json を設定
  headers: {
    'Content-Type': 'application/json'
  }
})
.then(function (response) {
  if(response.ok) { 
    // ステータスが ok ならばレスポンスボディを取得
    return response.text(); 
  } else {
    //ok でなければ例外を発生
    throw new Error(`失敗(例外発生): ${response.status}`);
  }
})
.then(function (text) {
  console.log(text); 
  //出力: PHP で JSON を受け取りました。 Name: Foo ID: 001 
})
.catch(function (error) {
  console.log(error);
})

PHP で POST された JSON を受け取るには file_get_contents() に "php://input" を指定します。php://input は読み込み専用のストリームで、 リクエストの body 部から生のデータを読み込むことができます。

そして json_decode() でJSON 文字列をデコード(PHP の変数に変換)します。その際、第2引数に true を指定して連想配列形式にします。

fetch_post_test2.php
<?php
//POST された JSON 文字列を読み込み
$json = file_get_contents("php://input");

//JSON 文字列を PHP の変数に変換
$data = json_decode($json, true);

//data.name
$name = $data["name"];
//data.id
$id = $data["id"];

//エスケープ
$name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
$id = htmlspecialchars($id, ENT_QUOTES, 'UTF-8');

//以下が出力されレスポンスボディとして受け取られる
echo "PHP で JSON を受け取りました。 Name: {$name} ID: {$id} ";