かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript (ES2015) 今更のPromise

JavaScriptを扱うにあたって避けては通れない道、Ajaxとかタイマーとかの非同期処理…
ES7のawait/asyncを学ぶ方が良いのでしょうが、jQuery.deferred辺りで止まってしまっていると、いきなりawait/asyncは辛いので、Promiseをざっくり理解してawait/asyncに置き換える方法で学習するのが良いかと思い今更のPromise入門です。

Promiseの基本

new Promise(function(resolve, reject) {...})

  • resolve ... 処理が成功したら呼び出す関数
  • reject ... 処理が失敗したら呼び出す関数

 

function hi(name) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(name) {
        resolve(`Hi, ${name}!`)
      } else {
        reject('ERROR name is empty!')
      }
    }, 100)
  })
}

.then()

Promise.prototype.then(successCallback, failureCallback)

返されるPromiseオブジェクトに.thenをメソッドチェーンさせることで、Promiseからresolve / rejectが呼び出されるまで待機して値を受け取れる

  • successCallback ... Promise.resolveが実行された時に呼び出される関数 (成功時に呼ばれる関数)
  • failureCallback ... Promise.rejectが実行された時に呼び出される関数 (失敗時に呼ばれる関数)
function onSuccess(res) {
  console.log('onSuccess', res)
}
function onReject(error) {
  console.log('onReject', error)
}

hi('星宮いちご').then(onSuccess, onReject)
// => onSuccess Hi, 星宮いちご!
hi().then(onSuccess, onReject)
// => onReject ERROR name is empty!

※ 非同期処理を同期的に実行したいときネストが深くなってしまう

hi('星宮いちご').then((res1) => {
  console.log('onSuccess 1', res1)
  hi('霧矢あおい').then((res2) => {
    console.log('onSuccess 2', res2)
    hi('紫吹蘭').then((res3)=> {
      console.log('onSuccess 3', res3)
    }, onReject)
  }, onReject)
}, onReject)
// => onSuccess 1 Hi, 星宮いちご!
// => onSuccess 2 Hi, 霧矢あおい!
// => onSuccess 3 Hi, 紫吹蘭!

thenのコールバック内でPromiseを返す関数をreturnすれば、Promise.thenをメソッドチェーンにすることができる

hi('星宮いちご').then((res1) => {
  console.log('onSuccess 1', res1)
  return hi('霧矢あおい')
})
.then((res2) => {
  console.log('onSuccess 2', res2)
  return hi('紫吹蘭')
})
.then((res3) => {
  console.log('onSuccess 3', res3)
})
// => onSuccess 1 Hi, 星宮いちご!
// => onSuccess 2 Hi, 霧矢あおい!
// => onSuccess 3 Hi, 紫吹蘭!

※ ただしメソッドチェーンしたthen内のコールバックはスコープが異なるので、前のthenのコールバックで受け取った値を直接参照することはできない

hi('星宮いちご').then((res1) => {
  return hi('霧矢あおい')
})
.then((res2) => {
  coneolse.log(res1)
  // => ReferenceError: res1 is not defined
})

※ エラーになった場合の処理が記述されてない場合、Promise.reject()が発生するとワーニングになってしまう。

hi()
.then((res1) => {
  console.log('res1')
  return hi()
})
.then((res2) => {
  console.log('Promise Complete')
})
// => UnhandledPromiseRejectionWarning: Unhandled promise rejection
// => DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

.catch()

Promise.prptpyupe.catch(onRejected)

Promiseが失敗した時に実行される節
エラーになった箇所以降のthenは実行されない

hi('神前美月').then((res1) => {
  console.log('onSuccess 1', res1)
  return hi() // Promise.rejectになる
})
.then((res2) => {
  console.log('onSuccess 2', res2)
  return hi('藤堂ユリカ')
})
.then((res3) => {
  console.log('onSuccess 3', res3)
})
.catch((error) => {
  // 共通のエラー処理
  console.log('Catch Error:', error)
})
// => onSuccess 1 Hi, 神前美月!
// => Catch Error: ERROR name is empty!

非同期処理を同期的に実行する時、特別なことがなければエラーはcatchで取るのが良さそう。

Promise.all

Promise.all() ... 引数で渡された全ての処理が完了したら結果の配列が返される。実行する処理に1つでもエラーがあるとエラーになる。

Promise.all([
  hi('神前美月'),
  hi('藤堂ユリカ'),
  hi('一ノ瀬かえで')
])
.then((res) => {
  // すべての非同期処理が完了したら、結果がArrayで返される
  console.log(res)
})
.catch((error) => {
  console.log(error)
})
// => [ 'Hi, 神前美月!', 'Hi, 藤堂ユリカ!', 'Hi, 一ノ瀬かえで!' ]

1つでもエラーがあると、最初にエラーになったものが返る

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 50, 'TASK1'))
const task2 = new Promise((resolve, reject) => setTimeout(reject, 200, 'TASK2'))
const task3 = new Promise((resolve, reject) => setTimeout(reject, 100, 'TASK3'))

Promise.all([
  task1,
  task2,
  task3
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
// => TASK3

Promise.race

Promise.race() ... 引数で渡された処理のいずれか1つが完了したら、完了した処理の値が返される。完了した最初の処理がエラーならエラー処理に流れる。※ 成功するものがあるか待つわけではない。

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '音城セイラ'))
const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '冴草きい'))
const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '風沢そら'))

Promise.race([
  task1,
  task2,
  task3,
])
.then((res) => {
 // 最初に完了した処理の返り値がそのまま渡される
 console.log(res)
})
.catch((error) => {
  console.log(error)
})
// => 風沢そら

最初に返されるものがエラー(reject)ならcatchに、並列処理中にエラーになるものがあっても、最初に返るのが正常な値(resolve)ならthenに処理が流れる

const task1 = new Promise((resolve, reject) => setTimeout(resolve, 100, '大空あかり'))
const task2 = new Promise((resolve, reject) => setTimeout(resolve, 50, '氷上すみれ'))
const task3 = new Promise((resolve, reject) => setTimeout(resolve, 10, '新条ひなき'))
const taskError = new Promise((resolve, reject) => setTimeout(reject, 30, 'ジョニー別府'))

Promise.race([
  task1,
  task2,
  taskError
])
.then((res) => console.log(res))
.catch((error) => console.log(`Error: ${error}`))
// => Error: ジョニー別府

Promise.race([
  task1,
  task2,
  taskError
  task3,
])
.then((res) => console.log(res))
.catch((error) => console.log(`Error: ${error}`))
// => 新条ひなき

Promise.resolve() と Promise.reject()

非同期処理がない場合など、わざわざPromiseオブジェクトを返す必要がない関数の場合、関数内でPromise.resolve()Promise.reject()を返すとと呼び出し元のPromiseに返すことができます。
returnしないと、関数が終了したundefinedが返される

function hi(name) {
  if( name.indexOf('ジョニー') === -1 ) {
    return Promise.resolve(`Hi, ${name}!`)
  } else {
    return Promise.reject(`ERROR: You are ${name}!`)
  }
}

Promise.all([
  hi('紅林珠璃'),
  hi('天羽まどか'),
  hi('黒沢凛')
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
//=> [ 'Hi, 紅林珠璃!', 'Hi, 天羽まどか!', 'Hi, 黒沢凛!' ]

Promise.all([
  hi('ヤミ'),
  hi('ミヤ'),
  hi('ジョニー別府'),
])
.then((res) => console.log(res))
.catch((error) => console.log(error))
//=> ERROR: You are ジョニー別府!

 
Promiseの基本的な使い方をまとめてみました。
実際の使われた方とかアンチパターンとかは、まだ解ってないですが... (その辺りはawait/asyncに移行しちゃってから考えたほうが良い気がします)


[参考]