JavascriptのPromiseについてご紹介します。「なぜPromiseを利用するのか?」「Promise状態の変更(resolve, reject)」「処理状態に応じたハンドリング方法(then, catch)」といったことを解説します。
Promiseを利用するメリット
Promiseを利用すると、非同期処理のコールバック地獄を回避することができます。
非同期処理とは?
時間の掛かる処理の完了を待たずに(同期せずに)、次の処理に進むことを非同期処理と言います。
const print = setTimeout(() => console.log('done'), 1000)
console.log('start')
// 非同期処理
print
console.log('end')
start
end
done
Promiseでコールバック地獄を回避
Promiseを 利用しなかった場合
と 利用した場合
を比べてみてみます。
利用しない場合
ネストが深くなり、読みづらいです。
/**
* @param {String} word
* @param {String} addWord
* @param {Function} callback
* @returns {number}
*/
const addString = (word, addWord, callback) => setTimeout(() => {
console.log(`${new Date() - startTime}ms`)
const data = word ? `${word}_${addWord}` : addWord
callback(data)
},
1000
)
const startTime = new Date()
addString('', 'aaa', data => {
addString(data, 'bbb', data => {
addString(data, 'ccc', data => {
console.log(data)
})
})
})
1003ms
2009ms
3014ms
aaa_bbb_ccc
利用した場合
メソッドチェーンを利用して、ネストが深くなるのを防ぐことができます。
/**
* @param {String} word
* @param {String} addWord
* @returns {Promise<any>}
*/
const addString = (word, addWord) => {
return new Promise((resolve, reject) => {
const timeoutFunction = data => {
console.log(`${new Date() - startTime}ms`)
return resolve(data)
}
setTimeout(
() => word ? timeoutFunction(`${word}_${addWord}`) : timeoutFunction(addWord),
1000
)
})
}
const startTime = new Date()
addString('', 'aaa')
.then(data => addString(data, 'bbb'))
.then(data => addString(data, 'ccc'))
.then(data => console.log(data))
1004ms
2009ms
3013ms
aaa_bbb_ccc
処理内部でPromise状態を変更
実行した処理が返すPromiseの 状態
を利用して、処理ハンドリングが行われます。
「どういった状態があるのか」「状態をどのように変更するのか」を確認します。
Promiseの状態
状態 | 安定 | 説明 |
---|---|---|
pending | × | 初期状態。操作未完了 |
fulfilled | ◯ | 操作完了 |
rejected | ◯ | 操作失敗 |
状態の変更
resolve関数
を利用すると fulfilled
の状態になります。reject関数
を利用すると rejected
の状態になります。
const sample_promise = x => {
return new Promise((resolve, reject) => {
x ? resolve('成功') : reject('失敗')
})
}
console.log('操作成功')
console.log(sample_promise(true))
console.log('------------------')
console.log('操作失敗')
console.log(sample_promise(false))
操作成功
Promise { '成功' }
------------------
操作失敗
Promise { <rejected> '失敗' }
(node:1820) UnhandledPromiseRejectionWarning: 失敗
(node:1820) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:1820) [DEP0018] 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.
失敗したとき、ハンドリングされていないというメッセージが表示されています。
Promise状態に応じてハンドリング
関数が返すPromiseの状態(成功 or 失敗)に応じて、ハンドリングする方法を確認します。
const sample_promise = x => {
return new Promise((resolve, reject) => {
x ? resolve('成功') : reject('失敗')
})
}
// 成功時呼ばれる関数
onFulfilled = data => {
console.log('成功しました')
console.log(data)
}
// 失敗時呼ばれる関数
onRejected = err => {
console.log('失敗しました')
console.log(err)
}
thenメソッド
第1引数で成功時の動作をハンドリング
sample_promise(true)
.then(onFulfilled, onRejected)
成功しました
成功
第2引数で失敗時の動作をハンドリング
sample_promise(false)
.then(onFulfilled, onRejected)
失敗しました
失敗
catchメソッド
catchメソッドでも、失敗時の動作をハンドリングできます。
さらに、catchメソッドを利用すると、thenの第1引数で失敗した場合もハンドリングすることができます。
// 成功時呼ばれる関数
onFulfilled = data => {
console.log('成功しました')
throw new Error()
}
// 失敗時呼ばれる関数
onRejected = err => {
console.log('失敗しました')
console.log(err)
}
onFulfilledのエラーをハンドリングできてない
sample_promise(true)
.then(onFulfilled, onRejected)
成功しました
(node:2765) UnhandledPromiseRejectionWarning: Error
onFulfilledのエラーをハンドリングできている
sample_promise(true)
.then(onFulfilled, onRejected)
.catch(onRejected)
成功しました
失敗しました
Error
finalyメソッド
ES2017時点では存在しません。
非同期処理の同時実行
Promise.all
複数の非同期処理を同時に実行して、全て完了したらthenメソッドが実行されます。
const sample_promise = (word, time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(word)
return resolve(word)
},
time
)
})
}
Promise.all([
sample_promise('aaa', 1000),
sample_promise('bbb', 500),
sample_promise('ccc', 2000)
])
.then(data => {
console.log(data)
})
bbb
aaa
ccc
[ 'aaa', 'bbb', 'ccc' ]
参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Promise.race
複数の非同期処理を同時に実行して、1つ成功した時点ですぐにthenメソッドが実行されます。
const sample_promise = word => {
return new Promise((resolve, reject) => {
resolve(word)
})
}
Promise.race([
sample_promise('aaa'),
sample_promise('bbb'),
sample_promise('ccc')
])
.then(data => {
console.log(data)
})
aaa
参考
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race