最近ようやくPromiseを触り始めました。わりと雰囲気で使ってしまっているので、ここらで自分のために整理を…。 ただ、基本的な例がすでに複雑というか、初心者にとってはムズい気がするので、超噛み砕いてみました。
最小構成
Promiseオブジェクトを初期化します。引数の関数は必須です。
new Promise(() => {})
引数が空だと、以下のように怒られます。
new Promise() TypeError: Promise resolver undefined is not a function
数値で誤魔化そうとしてもムダみたいです。
new Promise(10) TypeError: Promise resolver 10 is not a function
関数の引数
Promise初期化時の関数には、引数を2つ渡せます。 第一引数が"成功"のための関数、第二引数が"失敗"のための関数です。
new Promise((seikou, sippai) => {})
成功させる
成功させます。第一引数の関数を実行すると"成功"となります。
new Promise((seikou, sippai) => { seikou() })
失敗させる
失敗させます。第二引数の関数を実行すると"失敗"となります。
new Promise((seikou, sippai) => { sippai() })
ちなみに、正式には成功した状態を"fulfilled"、失敗した状態を"rejected"と呼ぶらしいです。
後処理も書いてみる
後処理も書いてみます。
new Promise(() => {}) .then(() => {}) // Promiseが"成功"ならthen以下が呼ばれる。 .catch(() => {}) // Promiseが"失敗"ならcatch以下が呼ばれる。
確認
成功したことを確認してみましょう。
new Promise((seikou, sippai) => { seikou() }) .then(() => { console.log('成功したのでこっちが呼ばれる') }) .catch(() => {})
一応、失敗したことも確認してみましょう。
new Promise((seikou, sippai) => { sippai() }) .then(() => {}) .catch(() => { console.log('失敗したのでこっちが呼ばれる') })
resolve, reject
一般的には、seikouにはresolve(解決), sippaiにはreject(拒絶)という名前が使われます。
new Promise((resolve, reject) => {})
Promiseを返す関数
Promiseを作って返す関数を作ってみます。
const createPromise = () => { return new Promise((resolve, reject) => {}) }
Promiseを成功させるかどうか。引数を渡して決めてみましょう。
const makePromise = (seikou) => { return new Promise((resolve, reject) => { if(seikou) { resolve() // 成功させる } else { reject() // 失敗させる } }) }
Promise(約束)を破ってみます。
const brokenPromise = makePromise(false) // falseを渡すとrejectが実行される brokenPromise .then(() => { // 今回はこっちは呼ばれない }) .catch(() => { // こっちが呼ばれる。 console.log('失敗させたのでこっちが呼ばれる') })
成功/失敗の理由を持たせてみましょう。
まずは関数を定義して、
const promiseAgain = (seikou, riyuu) => { return new Promise((resolve, reject) => { if(seikou) { resolve(riyuu) // 成功させる } else { reject(riyuu) // 失敗させる } }) }
実行します。
const brokenPromiseAgain = promiseAgain(false, 'やる気が起きない') brokenPromiseAgain .then((result) => { // 今回もこっちは呼ばれないけど、成功結果はresultとして渡される。 }) .catch((err) => { // rejectの理由がerrとして渡される console.log(`失敗理由は、${err}から。`) })
成功/失敗が確定しているなら
成功(resolve)だけを行うなら、以下のように書くことができます。
const keptPromise = () => { return Promise.resolve('必ず成功するPromise') }
実行します。
keptPromise() .then((result) => { console.log(result) // '必ず成功するPromise' }) .catch(() => { // こっちは実行されない。 })
これは以下と同様です。
const keptPromise = () => { new Promise((resolve) => { resolve('必ず成功するPromise') }) } keptPromise() .then((result) => { console.log(result) // '必ず成功するPromise' }) .catch(() => { // こっちは実行されない。 })
失敗(reject)だけも可能です。
const keptPromise = () => { return Promise.reject('必ず失敗するPromise...') }
Promise.resolve() - MDN Promise.reject() - MDN
複数のPromiseの実行を待つ
全てが成功する場合
Promise.all関数を使えば、複数のPromiseを処理できます。 Promise.all自体はPromiseを返すのですが、引数にとった複数のPromiseがすべてresolveされるまで、thenされません。
まずは、Promiseを返す関数をいくつか定義します。setTimeout関数を使い、ちょっとずつ成功をずらしています。
const promiseA = () => { return new Promise((resolve) => { setTimeout(() => { console.log('PromiseAが成功') resolve() }, 2000) // 一番遅い }) } const promiseB = () => { return new Promise((resolve) => { setTimeout(() => { console.log('PromiseBが成功') resolve() }, 1000) }) } const promiseC = () => { return new Promise((resolve) => { setTimeout(() => { console.log('PromiseCが成功') resolve() }, 1500) }) }
次に、Promise.all関数を使います。複数のPromiseを配列の形式で指定し、全てが成功したらかかった時間を表示します。
const startAllPromises = () => { const startTime = new Date() // 計測開始時間 Promise.all([ promiseA(), promiseB(), promiseC() ]).then(() => { const endTime = new Date() // 計測終了時間 console.log(`${(endTime - startTime) / 1000}秒で、全てのPromiseが成功`) }) } startAllPromises() // だいたい2秒くらいが表示される
一番成功の遅いPromiseAが終わったら、Promise.allの返すPromiseのthenが呼ばれます。
ちなみに、ABCのどれかが失敗(reject)した場合は、Promise.all()のthenは実行されません。
失敗ケースを補足する場合
Promise.allのあとにcatchをチェーンすればOKです。
失敗するPromiseを新しく定義します。
const promiseD = () => { return new Promise((resolve, reject) => { setTimeout(() => { console.log('PromiseDが失敗…') reject('D') }, 1700) }) }
失敗は1700ミリ秒後。PromiseCが成功する後、PromiseAが成功する前に失敗します。
const onePromiseFails = () => { const startTime = new Date() Promise.all([ promiseA(), promiseB(), promiseC(), promiseD() // ← 失敗するPromiseを追加 ]).catch((err) => { const endTime = new Date() console.log(`${(endTime - startTime) / 1000}秒で、promise${err}が失敗したので終了`) }) } onePromiseFails()
これで失敗の補足はできましたが、すでに走り出したPromiseが停止するわけではないようです。
コンソール出力。
PromiseBが成功 PromiseCが成功 PromiseDが失敗… 1.703秒で、promiseDが失敗 PromiseAが成功
Promise.allのthenが呼ばれないだけで、各Promiseはきちんと実行されるんですね。
基礎の基礎だけですが、以上です。 何か間違いなどありましたら、ご指摘いただけると幸いです。
素晴らしい資料
JavaScript Promiseの本 言わずと知れた、AzuさんのPromise解説。