読者です 読者をやめる 読者になる 読者になる

Node.js v8とasync function

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