初めに
今回はプロミスの静的メソッド、コールバックの内部関数をプロミスへの変換について、気になるところをまとめていきたいと思います。
今回の参考文章はこちらです。
Promise API - javascript.info
Promisification - javascript.info
Promise API
Promise.all(iterable)
使い方
-
iterable
:通常はPromise
の入った配列。 - 配列に全ての
Promise
がresolveされるまで待ち(fulfilled
状態)、新しいPromise
オブジェクトを格納した配列を返す。(then()
の引数となる)
注意点
- 配列にrejectされた
Promise
が出た場合、全体としてrejectされる。- 残りの
Promise
が実行するが、Promise.all()
の返り値Promise
はrejectになり、配列にほかに実行完了のPromise
結果を無視する。
- 残りの
-
Promise.all()
は繰り返し可能の配列にPromise
オブジェクトしか受け入れませんが、そうでない場合はPromise.resolve()
でラップしたものと扱われる。
Promise.allSettled(iterable)
使い方
-
iterable
:Promise
オブジェクトを格納した配列。 -
Promise.allSettled()
はresolveされる(fulfilled状態
)かrejectされる(rejected
状態)に関わらず、すべてのPromise
オブジェクトが完了したら新しいPromise
配列を返す。 - 配列にある各
Promise
オブジェクトから.status
で状態をチェックしたり、履行したPromise
なら.value
から値を、拒否したPromise
なら.reason
からエラーオブジェクトを取得する。
Promise.race(iterable)
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
3,
new Promise((resolve, reject) => resolve(4)),
]).then(result => {
console.log(result)
})
// 3
使い方
-
iterable
:Promise
オブジェクトを格納した配列。 - 最初の完了した値またはエラーを返します。それ以外は無視する。
Promise.any(iterable)
使い方
-
iterable
:Promise
オブジェクトを格納した配列。 - 最初の履行した(
fulfilled
)Promise
を待ち、その結果を返す。 - すべての
Promise
が拒否(rejected
)ならAggregateError
エラーオブジェクトを返す。errors
プロパティでエラーメッセージを取得できる。
Promise.resolve(value)/reject(error)
使い方
-
value
:任意の値 -
Promise.resolve()
は値をラップし、解決(resolve)されたPromise
オブジェクトを返す。 -
Promise.reject()
で拒否(reject)されたエラーのPromise
オブジェクトを返す。
Promisification
callback関数からPromise
を返す関数へ変換することです。
シンプルな例 です。
function loadScript(src, callback) {
let script = document.createElement('script')
script.src = src
script.onload = () => callback(null, script)
script.onerror = () => callback(new Error(`Script load error: ${src}`))
document.head.append(script)
}
上はコールバック、下はPromise
を返す。
function loadScript(src) {
return new Promise(resolve, reject => {
let script = document.createElement('script')
script.src = src
script.onload = () => resolve(script)
script.onerror = () => reject(new Error(`Script load error: ${src}`))
document.head.append(script)
})
}
let promise = loadScript('http://....')
promise.then(
script => console.log(`${script.src} is loaded`),
error => console.log(`Error: ${error.message}`)
)
基本的には関数をPromise
でラップして、Promise
の中にresolveかrejectを通してPromise
オブジェクト(あるいはエラーオブジェクト)を生成させ、その結果をthen
チェーンへ渡すプロセスです。可読性を高め、コントロールもしやすくなる方法です。
ほかも このように 書き換えてもいいらしいです。
let loadScriptPromise = function (src) {
return new Promise((resolve, reject) => {
loadScript(src, (err, script) => {
if (err) {
return reject(err)
} else {
resolve(script)
}
})
})
}
loadScript
は最初のコールバックsrc
とcallback
(ここでは中身はerr
とscript
)、関数の結果がPromise
を返すように全体をPromise
でラップし、err
が存在する場合はreject()
を呼び出しerr
の結果をPromise
で返すか、resolve()
でscript
を返す。
しかし毎回のように書き直すにはさすがに手間がかかるし、ラッパーが必要であれば別の関数(ヘルパー)でPromise
化してくれると楽になります。
function promisify(f) {
// at last, return the wrapped Promise to outside
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, result) {
if (err) {
return reject(err)
} else {
resolve(result)
}
}
// push the promisifying callback function to the end of args
args.push(callback)
// call the original function, set this promisifying function in it, and spread function in args
f.call(this, ...args)
})
}
}
(このコード一番下についてどうしてこう書くのか充分に理解していません。憶測のうえ解釈が適切かどうかもわかりません。とりあえずメモとして残ります。)
関数promisify
のすることが簡潔に言うと、Promise
でcallback
関数をラップして返す。
f
はもとの関数で、args
は後からラップされたcallback
の保管場所で、この匿名関数ではPromise
でラップした関数をargs
の最後に入れて、f
という元の関数に今にいるPromise
環境を呼び出し、args
で保管されている関数を展開して入れます。って言ってもとても理解しづらい解釈の仕方だと思いますが。
順番を変えて一番中から見ていくと、Promise
でラップした関数を最後にpromisify()
の返り値匿名関数の引数にcallback
を入れておいて、それからPromise
化したい関数f
をcall
で強制的に今のPromise
コンテキストに変えさせて、f
関数の引数に、args
に貯蔵したcallback
を展開して入れる。
説明ではf.call(this, ...args)
の作用はcallback
で結果を追跡するって書いてありますが、私にはちょっと難しすぎてまだ理解できていません...。
確かにf
の環境が一時的にPromise
化するためにcall
が必要、callback
もPromise
環境に依存していると分かっています。でもどうやって結果を追跡できるのかまだわかりません。ちゃんと動かせる環境ができたら補足しようと思います。