🤞
【自戒】Promiseを返す関数を反復処理するときの注意事項
はじめに
※Promiseオブジェクトをちゃんと理解していなかった自分に対しての戒めを含みます。
事象について
表題の通りですが、 Promiseを返す処理を反復させ、その後に実行すべき処理がその反復処理の前に実行されてしまうという事象でした。
例を挙げると、下記のようなコードです。
const strs = ['test1', 'test2', 'test3'];
const asyncFuncStr = (str) => {
return new Promise((resolve, reject)=> {
setTimeout(() => {
console.log(str)
resolve()
}, 2000)
})
}
const mapFunc = async() => {
return strs.map((s) => asyncFuncStr(s))
}
mapFunc().then((res) => {
console.log('finished')
});
2秒後にそれぞれの文字列をコンソールに出力する関数「asynFuncStr」=「Promiseを返す処理」です。
これを見て「だめやん」と思った方は、この先読む必要がないので離脱してください...
問題点について
上記においての問題点は、関数「mapFunc」です。
その中でもreturnしているものに問題があります。
実際にreturnされているものを見てみると、下記のような返り値になっています。
// [object Array] (3)
[// [object Promise]
{},// [object Promise]
{},// [object Promise]
{}]
Promiseのオブジェクトが三つ返ってきていますね。
つまり、resolveされていない状態で値が返ってきてしまっています。意味ないですよね。
ではどうするか?
解決策について
今回の悪い点はmap内の反復処理の一つ一つを解決せずに返してしまっている、という点です。
そこで、下記のように修正します。
const strs = ['test1', 'test2', 'test3'];
const asyncFuncStr = (str) => {
return new Promise((resolve, reject)=> {
setTimeout(() => {
console.log(str)
resolve()
}, 2000)
})
}
const mapFunc = async() => {
return Promise.all(strs.map((s) => asyncFuncStr(s)))
}
mapFunc().then((res) => {
console.log('finished')
});
変更点は一つだけで、mapの処理を「Promise.all」で囲うだけです。
先程の返り値も下記のように変わります。
// [object Array] (3)
// [undefined,undefined,undefined]
Promise.allについてはMDNを参照してください。
めちゃくちゃ望ましい挙動ですよね。
まとめ
あんまりこのようなPromiseを返すような関数をラップして使うことがなかったので、つまずきました。
aync/awaitの恩恵に怠けず、ちゃんとPromiseオブジェクトから勉強すべきですね。
Discussion
StaticなメソッドがPromiseからは提供されています。
Promise.reject
,Promise.resolve
を使ってもいいかもと思いました。demo code.