ECMA Script6(JavaScript)のPromiseオブジェクトの使い方

ECMA Script6(JavaScript)のPromiseオブジェクトの使い方

ECMA Script6(JavaScript)のPromiseオブジェクトの使い方についてです。

ECMA Script6(JavaScript)のPromiseオブジェクトの使い方

Promiseメソッドは非同期処理を、同期処理のように扱えるオブジェクトです。

Promiseオブジェクトの概念は一番下の参考サイトを見ると非常にわかりやすいです。

Promiseオブジェクトはまずnewして使います。

let promise = new Promise((resolve, reject) => {
  // 非同期処理
  // 成功時resolve、失敗時rejectを呼ぶ
  return resolve(100);// returnはなくても良い
});

promiseのthenメソッド

Promiseオブジェクトにはthenメソッドがあります。

thenメソッドには引数が2つあり、非同期処理が成功した場合は、第一引数のonFulfilled関数を実行します。

非同期処理が失敗した場合は、第二引数のonRejected関数を実行します。

以下、記述例です。

let promise = new Promise((resolve, reject) => {
  resolve(100);
});

promise.then(function onFulfilled(val){
  console.log(`成功:${val}`);
},function onRejected(err){
  console.log(`失敗:${err}`);
});

結果は以下のようになります。

成功:100

Promiseオブジェクトがresolve(成功)しているのでonFulfilled関数が呼ばれています。

逆に、Promiseオブジェクトがreject(失敗)している場合の結果を見てみます。

let promise = new Promise((resolve, reject) => {
  reject(100);
});

promise.then(function onFulfilled(val){
  console.log(`成功:${val}`);
},function onRejected(err){
  console.log(`失敗:${err}`);
});

結果は以下のようになります。

失敗:100

但し、thenメソッドに必ず失敗時の処理を記述しないといけないわけではありません。

通常は成功時だけの第一引数だけを記述したりします。以下はthenメソッドの第二引数を省略した例です。

let promise = new Promise((resolve, reject) => {
  resolve(100);
});

promise.then(function onFulfilled(val){
  console.log(`成功:${val}`);
});

省略しても結果は以下のようになります。

成功:100

promiseのcatchメソッド

catchメソッドの引数は一つで、onRejected関数を指定します。その為、PromiseオブジェクトのthenメソッドではonRejected関数が省略できるというわけです。

catchメソッドが実行される例です。

let promise = new Promise((resolve, reject) => {
  reject(100);
});

promise.then(function onFulfilled(val){
  console.log(`成功:${val}`);
});

promise.catch(function onRejected(err){
  console.log(`失敗:${err}`);
});

結果は以下のようになり、thenメソッドが呼ばれていないのが確認できます。

失敗:100

thenメソッドのメソッドチェイン

thenメソッドはpromiseオブジェクトを返すのでメソッドチェインすることができます。

以下、記述例です。

const p = [{'key1':'val1','key2':'val2'}];
const promise = Promise.resolve(p);

promise.then(function(val) {
  return val;
}).then(function(val) {
  console.log(val);
});

最初のthenメソッドでresolve(val)ではなく、valをそのまま返しているので、この値が次のthenメソッドに渡されます。

結果は以下のようになります。

[ { "key1": "val1", "key2": "val2" } ]

最初のthenメソッドでなにもしない場合は面白い動きをします。

const p = [{'key1':'val1','key2':'val2'}];
const promise = Promise.resolve(p);

promise.then(function(val) {

}).then(function(val) {
  console.log(val === undefined);
});

次のthenメソッドの引数はundefinedになってしまいます。結果はtrueです。

要するに、thenメソッド内ではresolve,rejectをしてはいけなく、returnさえすれば次のthenメソッドにデータ結果が渡される、ということです。

以下、よく書く記述例です。

const p = new Promise((resolve,reject)=>{
  return resolve('a');
}).then((data) =>{
  return data;
}).then((data) =>{
  data = data + 'b';
  console.log(data);
}).then((data) =>{
  throw Error('エラーだ');
}).catch((error) =>{
  console.log(error.message);
})

結果は以下のようになります。

ab
エラーだ

then内でresolveするとエラーとなる

then内で、resolve(data);というような記述方法で詰まったことがあります。

以下、その例です。

let promise = new Promise((resolve, reject) => {
  resolve(100);
});

promise.then((data) => {
  console.log(data);
  resolve(data);
}).catch((error) => {
  console.log(error);
});

このコードはthenの中に入り、resolveしていますが、そんな定義はないよ、と怒られてcatch句に入ってしまいます。

結果は以下のようになります。

100
ReferenceError: resolve is not defined

Promise.all()メソッド

Promiseオブジェクトにはstaticメソッドがいくつか用意されています。

all()メソッドはstaticとなりますので、newせずにそのまま使用できます。

allメソッドの説明はすごく難しいですが、引数にPromiseオブジェクトを要素とした配列を持ちます。allの引数は配列である、という事を忘れがちになりますが、必ず配列でなければいけませんので注意が必要です。

Promiseの要素を持つ配列と言ったらよいでしょうか。それぞれのPromiseオブジェクトは非同期に実行されますが、実行結果の順序は配列の順序どおりであることが保証されています。

配列の各要素であるPromiseオブジェクトが全てresolveまたはrejectを返したら、Promise.all.thenメソッドが実行される、という仕組みです。

以下、例です。

let promise1 = new Promise((resolve, reject) => {
  resolve(100);
});

let promise2 = new Promise((resolve, reject) => {
  resolve(200);
});

Promise.all([promise1, promise2]).then(function onFulfilled(val){
  console.log(`成功:${val}`);
  console.log(val instanceof Array); // 配列であることがわかる
  console.log(`成功:${val[1]}`);// 配列の要素を表示
}).catch(function onRejected(err){
  console.log(`失敗:${err}`);// 呼ばれない
});

結果は以下のようにcatchメソッドは呼ばれず、thenメソッドが呼ばれ、また引数は配列としてcallback関数に渡ります。

成功:100,200
true
成功:200

ということは片方のPromiseオブジェクトがrejectを返した場合はどういう動きをするかが気になります。

以下、実行例です。

let promise1 = new Promise((resolve, reject) => {
  resolve(100);
});

let promise2 = new Promise((resolve, reject) => {
  reject(200);
});

Promise.all([promise1, promise2]).then(function onFulfilled(val){
  console.log(`成功:${val}`);
  console.log(val instanceof Array);
  console.log(`成功:${val[1]}`);
}).catch(function onRejected(err){
  console.log(`失敗:${err}`);
});

結果は以下のようにcatchメソッドが実行されます。

失敗:200

allの配列のPromiseオブジェクトのいずれかがrejectを返す場合はthenメソッドは実行されずcatchメソッドが実行されるようです。また、引数はrejectした値のみが渡るようです。

Promise.allの特徴として、配列の要素にPromiseオブジェクトを格納すると、それぞれのPromiseオブジェクトは非同期に実行されます。

但し、戻り値は、配列の要素順になることが保証されていますので、非常に便利です。

以下、例です。

let p1 = new Promise((resolve, reject) => {
  resolve(100);
});
let p2 = new Promise((resolve, reject) => {
  resolve(200);
});
Promise.all([p1,p2]).then((data) => {
  console.log(data[0]);
  console.log(data[1]);
});

結果は以下のようになります。

100
200

Promise.allで配列要素が1つの場合はPromise.allを使用しない

Promise.allは複数プロミスを同時に実行できるのがメリットです。なので配列要素が1つの場合はPromise.allをわざわざ使用する必要はありません。

プロミスを変数に格納してthenすれば良いです。

let promise = new Promise((resolve, reject) => {
  resolve(100);
});
promise.then((data) => {
  console.log(data);// 100と表示される
});

Promise.raceメソッド

Promise.raceメソッドはPromise.allと非常によく似ていますが、いずれかのPromiseオブジェクトがresolveを返せばthenが呼ばれます。逆にrejectを返せばcatchが呼ばれます。

Promise.allメソッドとの違いはcallback関数に渡る戻り値に違いがあります。

以下、実行例です。

let promise1 = new Promise((resolve, reject) => {
  resolve(100);
});

let promise2 = new Promise((resolve, reject) => {
  console.log('実行2');
  reject(200);
});

let promise3 = new Promise((resolve, reject) => {
  console.log('実行3');
  resolve(300);
});

Promise.race([promise1, promise2, promise3]).then(function onFulfilled(val){
  console.log(`成功:${val}`);
  console.log(val instanceof Array);
  console.log(`成功:${val[1]}`);
}).catch(function onRejected(err){
  console.log(`失敗:${err}`);
});

結果は以下のようにpromise1の値しか返ってきません。但し、promise2もpromise3も実行はされるようです。

実行2
実行3
成功:100
false
成功:undefined

Promise.resolveメソッド

Promise.resolveメソッドは完了するpromiseオブジェクトを返します。

以下、例です。

const p = [{'key1':'val1','key2':'val2'}];
  Promise.resolve(p).then(function(val) {
  console.log(val);
});

結果はthenメソッドが呼ばれます。

[ { "key1": "val1", "key2": "val2" } ]

書き換えると以下のようにも記述できます。

const p = [{'key1':'val1','key2':'val2'}];
const promise = Promise.resolve(p);

promise.then(function(val) {
  console.log(val);
});

結果は同じです。

[ { "key1": "val1", "key2": "val2" } ]

参考サイト

JavaScript Promiseの本

UnhandledPromiseRejectionWarning: Unhandled promise rejection
このエラーがでる場合が時々あります。

詳細を知りたい場合、以下を記述すると詳細なエラーを出力してくれるようになります。

process.on('unhandledRejection', console.log);

Promise.rejectメソッド

このメソッドはthenメソッド内で使用するとcatchに遷移します。

以下、例です。

let promise = new Promise((resolve, reject) => {
  resolve(100);
});

promise.then((data) => {
  return Promise.reject('reject!!');
}).catch((err) => {
  console.log('error',err);
});

結果は以下のようにcatch内に遷移します。

error reject!!

色々英語のサイトを見ていると、どうもPromise.reject()というのは以下のショートカットのようなものらしいです。

let p = new Promise((resolve, reject) => {
  reject();
});

For async tests and hooks, ensure “done()” is called; if returning a Promise, ensure it resolves.

…プロミスオブジェクトがresolveまたはrejectしていないままの状態の場合にこのエラーが発生します

chainメソッド

Promise.prototype.chainメソッドが存在します。

非推奨のメソッドですが、thenメソッドのエイリアスのようです。

the feature of method Promise.prototype.chain in chrome
a method in chrome browser named Promise.prototype.chain. I cannot find any documentation about this method. Anybody kno...

thenメソッド内でthrowする

thenメソッド内でthrowすると、catch句に遷移します。

let p = new Promise((resolve, reject) => {
  resolve('test');
});
p.then((data) => {
  throw new Error('エラー');
}).then((data) => {
  console.log(data);// このthenは通らない
}).catch((error) => {
  console.log(error);
});

結果はcatch句でエラー内容が出力されます。

Error: エラー

配列のforEachメソッド内でPromiseは使用しない

forEach内でPromise.reject();などとすると、forEachのcallback関数に戻るため、「Unhandle~~」とエラーがでます。

配列をループしたい場合はfor-ofを使用すれば、その中でrejectしても意図した動作となります。

Promiseの上手な使い方

Promiseを上手く使うには、thenに関数を渡し、それをthenで繋いでいく、といった方法が良いように思います。以下のような書き方がきれいだと思います。

ちなみにbind(this)のあとに、カンマ区切りで引数を渡すことができます。

let validate = this._validate.bind(this, {id, name, inputData}); // _validateはプライベートメソッド
let getDb = this._getDb.bind(this); // _getDbはプライベートメソッド
let calcData = this._calcData.bind(this); // _calcDataはプライベートメソッド
return new Promise(function (resolve, reject) {
  Promise.resolve()
  .then(validate) // 引数チェックする関数を格納した変数
  .then(getDb) // データ取得処理
  .then(calcData) // データ計算処理
  .then(resolve) // resolveを返す
  .catch(reject); // rejectを返す
}).catch((err) => {
  console.error('エラーが発生しました', err);
  return Promise.reject(err);
});

コメント

タイトルとURLをコピーしました