Node.jsで同期処理を行う際にちょっとハマったのでメモ
やりたかったこと
- Socket.ioのイベント内で、Redisにアクセスして、結果をゴニョゴニョして、Socket.ioでクライアントに結果を返す処理
ちょっとハマったポイント
- Node.jsはノンブロッキングIOなので、関数が非同期に実行され、ゴニョゴニョする前の結果をSocket.ioで返却してしまっていた。
最初のソースコード
var redis = require("redis");
var socketIO = require("socket.io");
var express = require("express");
client = redis.createClient(port, 'ip');
//サーバー構築
var app = express();
var port = process.env.PORT || 5000;
app.use(express.static(__dirname + "/public"));
var server = http.createServer(app);
server.listen(port);
// Socket.IOサーバーを構築する
var io = socketIO.listen(server);
// websocketのイベント
io.sockets.on("connection", function (socket) {
// イベントを受け取った時
socket.on("event1", function (data) {
//返却するリスト
var returnArray = [];
//Redisからデータを取得。ここでは複数のユーザープロフィールを取得したとする
client.MGET(["userprofile1","userprofile2"], function(err, profiles){
profiles.forEach(function (profile, i) {
//ここでプロフィールをゴニョゴニョした結果をreturnArray に詰める
returnArray.push(profile);
});
});
//WebSocketで送信
io.sockets.emit("retEvent1", returnArray);
});
});
こんな感じで最初書いてましたw
こうすると、
returnArray
は空っぽのまま、WebSocketで送信されてしまいます。
Node.jsはノンブロッキングIOなので、client.MGET
が完了するのを待たずに、
次のio.sockets.emit
が実行されてしまうためです。
これは、やりたいことと違うので、asyncモジュールを使って、処理を同期的に書き換えます。
これくらいの処理では、async使わずともできますが、client.MGET
の中でさらに、Redisにアクセスして
ゴニョゴニョしてってやっていくと、ネストがどんどん深くなり、可読性、メンテナンス性が下がるのでオススメしません。
asyncを使うと、ネストを深くせずに、処理を記述していけます。
ネストがどんどん深くなる例
client.MGET(["userprofile1","userprofile2"], function(err, profiles){
client.GET("hoge", function(err, hogeval){
// 何か処理
client.GET("fugafuga", function(err, fugaval){
// 何か処理
});
});
});
asyncを使ったソースコード
var redis = require("redis");
var socketIO = require("socket.io");
var express = require("express");
//async モジュールのインポート
var async = require('async');
client = redis.createClient(port, 'ip');
//サーバー構築
var app = express();
var port = process.env.PORT || 5000;
app.use(express.static(__dirname + "/public"));
var server = http.createServer(app);
server.listen(port);
// Socket.IOサーバーを構築する
var io = socketIO.listen(server);
// websocketのイベント
io.sockets.on("connection", function (socket) {
// イベントを受け取った時
socket.on("event1", function (data) {
async.waterfall([
function(callback) {
client.MGET("userprofiles", function(err, profiles){
//次の処理を呼び出す。callbackを呼ばないと次の処理は実行されない
callback(null,profiles);
});
},
function(profiles, callback) {
var returnArray = [];
profiles.forEach(function (profile, i) {
//ここでプロフィールをゴニョゴニョした結果をreturnArray に詰める
returnArray.push(profile);
});
callback(null,returnArray);
},
],
function(err, returnArray) {
//WebSocketで送信
io.sockets.emit("retEvent1", returnArray);
});
});
});
async.waterfallを使うことで、ネストさせずに処理を記述していくことができます。