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に変換するユーティリティを導入する予定らしい。