Node.js v8.0.0のリリースが近い。v8は10月にリリースされるLTSの候補版、マイルストーンとして非常に重要なリリースとなる。
LTSラベルは4系列がArgon(アルゴン)、6系列がBoron(ホウ素)だったので、8系列のLTSはCarbon(炭素)あたりが有力なんすかね? むしろ10系列はどうするんだろう。Dで始まる元素なんて知らんぞww
※ v8と書くとJSエンジンのV8と紛らわしいので、以後はv8.xと書きます。
async function (asyna & await)
Node.js v8.xの目玉機能、それは何と言ってもasync function。こいつはv7.6で既に使用可能になっているのだが、本格的に普及するのはLTSとなるv8.x以降だろう。正気ならv7.xとか本番環境で使わないっしょ? 変態でない普通のプログラマが使うようになるのはLTSからですよ。
んで、async functionだが、こいつは非同期処理を同期的なコードのように書けるようにするもの。もう少し具体的にいうと複数のPromiseの呼び出しを簡潔に記述できるようにするものだ。例を見た方が早い。
DBからユーザデータをfindし、ユーザがいれば、さらにそのユーザのオプションを取得してユーザにセットして返す。ユーザがいなければエラーを返すという非同期処理を行う関数 findUserAndOptions を想定する。DBアクセスはPromiseを返すAPIによって行われるものとする。
こいつをPromiseで書くと
'use strict'; const db = require('./stub'); function findUserAndOptions(id) { return db.findUser(id).then((user) => { if (!user) { return Promise.reject(new Error(`ID ${id} not found`)); } return user; }).then((user) => { return db.findUserOptions(id).then((options) => { user.options = options; return user; }); }); } findUserAndOptions(1).then((user) => { console.log(JSON.stringify(user)) }).catch((err) => { console.error(err.message); });
単純な処理のくせに複雑で読みにくいコードだ。これをasync functionで書き直すと
'use strict'; const db = require('./stub'); async function findUserAndOptions(id) { const user = await db.findUser(id); if (!user) { throw new Error(`ID ${id} not found`); } const options = await db.findUserOptions(id); user.options = options; return user; } findUserAndOptions(1).then((user) => { console.log(JSON.stringify(user)) }).catch((err) => { console.error(err.message); });
単純明快、非常にわかりやすい、新卒研修のサンプルかと思うほど簡単なコードになった。
Aysnc Function自身は関数内でreturnされた値で解決されるPromiseを返す。なので呼び出し側は単なるPromiseとして扱えばいい。もちろん、Promiseを返すのでAsync Functionを他のAsync Functionの中でawaitと共に利用するのもOKだ。
DBアクセス代わりのstubはこんな感じ。Promiseを返しているところに注目。Async Functionは単純なPromiseによる非同期処理を組み合わせて、複雑な処理を行う場合に威力を発揮する。今後はDBなどの非同期アクセスはcallbackではなく、Promiseで処理するのが主流になるだろう。既にmongoDBのドライバなんかはcallbackを渡さなければPromiseを返すようになっている。
exports.findUser = function(id) { return new Promise((resolve, reject) => { setImmediate(() => { if (id === 1) { resolve({ name: "なまえ" }); } else { resolve(null); } }); }); }; exports.findUserOptions = function(id) { return new Promise((resolve, reject) => { setImmediate(() => { if (id === 1) { resolve([ "オプション1", "オプション2", ]); } else { resolve([]); } }); }); };
既存のNodeのAPI、fs.readFileのようなコールバック前提のAPIはどうすんのよ?という話だが、Promiseベースに作り直すのではなく、util.promisify というコールバックベースのAPIをPromiseに変換するユーティリティを導入する予定らしい。