◆ 昔のタイプの非同期関数を Promise を返すタイプの非同期関数にしてくれる

すごい今更ですが Node.js 標準機能にこんなのあったのですね
bluebird あたりでそんなことしてる人はみたことありましたが わざわざパッケージ入れてまでするものじゃないと自分でそれっぽいもの作ってました

Promisify の機能ですが Node.js に昔からある関数に多い err と result の引数を受け取るコールバック関数を最後の引数として必要とするものを Promise を返すようにしてくれるというものです

昔のコールバック関数タイプ

Promise がない頃の Node.js の関数はエラーが起きうる非同期処理は

function callback(err, result) {
// 終わったらやること
}

という引数の関数を関数に渡す必要がありました

適当にもってきた公式ページのサンプルです

fs.readFile('/etc/passwd', (err, data) => {
if (err) throw err;
console.log(data);
});

第一引数になにかあればエラーが起きたときのエラーオブジェクトで 第一引数が空なら正常に終了したということです
その場合の結果は第二引数に渡されます

Promisify

promisify を使うとこういうタイプの関数を Promise を返す関数に変換してくれます

util.promisify((a, b) => a < 0 ? b(a) : b(null, a))(100)
.then(x => console.log("ok", x))
.catch(x => console.log("ng", x))
// ok 100

util.promisify((a, b) => a < 0 ? b(a) : b(null, a))(-100)
.then(x => console.log("ok", x))
.catch(x => console.log("ng", x))
// ng -100

promisify に関数を渡して返ってくる関数にコールバック以外の値を渡すだけです
第一引数があれば Promise が rejected となり catch が呼び出されて 第一引数がなければ Promise が resolved となり then で第二引数を受け取れます

今回 promisify に渡してる関数は 昔ながらの関数をエミュレートするもので第二引数の b にコールバックを渡して a が 0 以上なら成功でコールバック関数 b の第二引数に a の値が渡されます
0 未満なら失敗でコールバック関数 b の第一引数に a の値が渡されます

さっきの readFile なら

const data = await util.promisify(fs.readFile)("/etc/passwd")
console.log(data)

と書けるようになります

fs の場合は Node.js 10 からは fs/promises に最初から Promise を返してくれる関数があるようなので使わなくても良さそうです

昔作ってたの

昔自分で作って使ってたのはちょっとだけ使い方が違います

fn(100, 200, (err, result) => {
console.log(result)
})



const result = await p(next => fn(100, 200, next))
console.log(result)

のように使います

Promisify では関数を変換した関数を作って それをコールバック関数なしで呼び出すだけでした
これはコールバック関数として渡すべきものを引数で受け取って それをコールバック関数として渡します
例ではわかりやすいように next という名前で受け取って 最後の引数として渡しています
この next が Promise 化してくれる関数です
自分で next を入れるので場所が最後じゃなくても良い という作りなのですが最後じゃないケースなんて全然ないので最後にしか使っていません
setTimeout などはコールバック関数を最初に渡しますが引数が違いますし

そうなると最後固定にして Promisify と同じようにしてもよかったのですが 一応 bind 忘れの this 問題の心配もないしこれでいいかと使っていました
Promisify にする場合には xxx.yyy のようなプロパティを渡すときに注意が必要ですね

ちなみにこの p という関数は単純で

export default f => new Promise((ok, ng) => f((err, result) => err ? ng(err) : ok(result)))

これを別ファイルにして p という名前で import しているだけです


今回は引数とかコールバック関数とか文だけだと何が何を表してるのか自分でもすごくわかりづらくなってしまいました
なのでコード見たほうがわかりやすいかもしれません