初めに
今回はプロミスの静的メソッド、コールバックの内部関数をプロミスへの変換について、気になるところをまとめていきたいと思います。
今回の参考文章はこちらです。
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環境に依存していると分かっています。でもどうやって結果を追跡できるのかまだわかりません。ちゃんと動かせる環境ができたら補足しようと思います。