Taming the asynchronous beast with ES7
babel の experimental に ES7 の Async/Await が入ったというので、さっそく導入してみた。対象は画像を点字を変換するわりかしどうでも良いモジュール。
導入前
ファイル読み込みや画像の変換に非同期処理を多用しており、node.js スタイルの 非同期API(コールバックを渡すやつ) を prominence というユーティリティ関数で Promise 化していたが、行ごとに then が出てきたり、複数の値を渡すために Promise.all を使ったりと、あまり読みやすいとは言えないコードだった。(コールバック地獄になるよりはマシだと思うけど..)
import gm from "gm";
import pngparse from "pngparse";
// 画像を点字に変換する
let convert = (src, opts) => {
// 画像を GraphicsMagick の形式に変換
return Promise.resolve(gm(src)).then((im) => {
// サイズを取得
return Promise.all([ im, prominence(im).size() ]);
}).then(([ im, size ]) => {
// 画像を加工 (省略)
return im;
}).then((im) => {
// PNGに変換
return prominence(im).toBuffer("png");
}).then((buffer) => {
// PNGをパース
return prominence(pngparse).parse(buffer);
}).then((png) => {
// PNGを点字に変換 (省略)
return "点字";
});
};
導入準備
まず babel をアップデートして、devDependencies から dependencies に移動。
"dependencies": {
"babel": "^4.7.8"
}
.babelrc
で ES7 機能を使うフラグを設定。
{
"experimental": true
}
async/await を使うファイルで babel/polyfill
をインポートする。これがないと実行時に "regeneratorRuntime is not defined" というエラーが出る。
import "babel/polyfill";
導入後
await を使う関数自体を async function
にして、非同期処理を行う関数呼び出しに await
を追加するだけ。手続き型言語!!という感じで、コードの見通しは非常に良くなったと思う。使う側のコードに変更は不要で、書き換える前と同じインターフェース(この場合はPromise)で使える。
import gm from "gm";
import pngparse from "pngparse";
// 画像を点字に変換する
export async function convert(src, opts) {
// 画像を GraphicsMagick の形式に変換
let im = gm(src);
// サイズを取得
let size = await prominence(im).size();
// 画像を加工 (省略)
// PNGに変換
let buffer = await prominence(im).toBuffer("png");
// PNGをパース
let png = await prominence(pngparse).parse(buffer);
// PNGを点字に変換 (省略)
return "点字";
};
知見
事前にPromise化していた効果が大きかったのか、コードの書き換えはあっという間にできた。最近 Promise がいまいち使いづらいと思い始めていたけど、使い続けるモチベーションにはなった。
失ったもの
- lint (jshint) が使えなくなったので消した
diff