nodejs+express 1つのとても経典的な問題--非同期取得データテンプレートレンダリング


前言
多くの場合、nodejsはいくつかの非同期的な操作があります.例えば、ファイルを読んで書きます.例えば、httprequestを使ってバックグラウンドのデータを取得してレンダリングしますが、nodejsの特性のため、多くの場合は非同期です.どのように解決しますか?
問題コード
/**
 *       api。
 *
 */
var request = require('request');
var Settings=require('../settings.js');
var fs = require('fs');
var sysUtil = require('../util/SystemUtil.js');
var logger=sysUtil.Logger;

exports.getMenuList=function(callback){
    logger.info("  url:"+Settings.WebApi.ApiHost+Settings.WebApi.IndexMenuApi);
    var _url=Settings.WebApi.ApiHost+Settings.WebApi.IndexMenuApi;
    //_url="http://www.baidu.com";
    request(_url, function (error, response, body) {
        if (!error && response.statusCode == 200) {

            if(callback){
                callback(response,body);
            }
        }
        else{

            console.log("       。");
        }
    }).pipe(fs.createWriteStream('./test/file1.txt'));
};

var express = require('express');
var router = express.Router();
var Settings=require("../settings");
var sysUtil=require("../util/SystemUtil.js");
var logger=sysUtil.Logger;
var fs=require("fs");
/* GET home page. */
router.get('/', function(req, res, next) {
  //console.log("================");
  //console.log(Settings);

  //--        。
  var CategoryService=require("../service/CategoryService.js");
  var _menuList={};

  CategoryService.getMenuList(function(res1,body1){
    _menuList=JSON.parse(body1);
    //fs.writeFile("./test/file1.txt",JSON.stringify(_menuList),function(){});

  });

  res.render('index', { title: 'Express' ,menuList:_menuList});
});

module.exports = router;
ok、私達はテンプレートをレンダリングする時、menuListは永遠に空です....
ソリューション
つの比較的に実行可能な方法はdeffered、promise方案を使うので、もちろん、requestはこれもdeffered promiseでカプセル化する必要があって、さもなくば死人を書きます.以下はプロジェクトに適用される成熟したプログラムコードの一部です.
/**
 *       request          ,  ,   promise   。
 */
var logger = require('../util/logHelper.js').Logger;
var fs=require("fs");
var Q = require('q');
var util=require("../util/util.js");
var request = require('request');
var Extend=require("util")._extend;
var RequestHelper=function(){
    this.taskList=[];
};
RequestHelper.prototype.addTask=function(_opt){

        this.taskList.push(_opt);
}

RequestHelper.prototype.request=function(_opts){
    var _deferred= Q.defer();
    var _i_settings={
        url:""
        ,method:"get"
        ,data:{}
        ,dataType:"json"
        ,error:function(error){
        }
        ,success:function(sdata){
        }
    };
    _i_settings=Extend(_i_settings,_opts);
    if(_i_settings.method.toLocaleLowerCase()=="get"){
        var _realUrl=util.Json2URL(_i_settings.url,_i_settings.data);
        logger.info(_realUrl);
        var req2=request(_realUrl, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                var _res=body;
                try{
                if(_i_settings.dataType=="json"){
                    _res=JSON.parse(_res);
                }
                }
                 catch (ex){
                     var _error_msg="nodejs can not trans this data to JSON format: "+body;
                     logger.error("json translate error:"+_i_settings.url);
                     logger.error(_error_msg);
                     logger.error(body);
                     _deferred.reject(_error_msg);

                     return;

                }
                _deferred.resolve(_res);}

            else{
                var _error_msg="request helper error: can not request url:"+_realUrl+"";
                logger.error(_error_msg);
                logger.error(body);
                _deferred.reject(error);
            }
        });
    }
    else{
        //--post  。
        request.post({
            url:_i_settings.url
            ,form:_i_settings.data
        },function(err,response,body){
            if (!err && response.statusCode == 200) {
                var _res=body;
                try{
                    if(_i_settings.dataType.toLocaleLowerCase()=="json"){
                        _res=JSON.parse(_res);
                    }
                }
                catch (ex){
                    var _error_msg="nodejs can not trans this data to JSON format: "+body;
                    logger.error("json translate error:"+_i_settings.url);
                    logger.error(_error_msg);
                    logger.error(body);
                    _deferred.reject(_error_msg);

                    return;

                }
                _deferred.resolve(_res);}

            else{
                var _error_msg="request helper error: can not request url:"+_i_settings.url+"";
                logger.error(_error_msg);

                logger.error(err);
                logger.error(body);

                _deferred.reject(err);
            }
        });

    }


    var _promise= _deferred.promise;
    _promise.then(_i_settings.success,_i_settings.error);
    return _promise;
};


RequestHelper.prototype.run=function(onDone){
  var me=this;
    var taskList=this.taskList;
    if(taskList.length<=0){

        if(onDone){
            onDone();
        }
        return;
    }
    var _now_taskReq=[];
    for(var i=0;i< this.taskList.length;i++){
        var _taskItem={
            url:""
            ,success:function(parameter){

            }
            ,error:function(parameter){

            }
            ,method:"get"
            ,data:{}
            ,dataType:"json"
        };
        Extend(_taskItem,this.taskList[i]);
        var _promise=me.request(_taskItem);
        _now_taskReq.push(_promise);
        }
    //--               。
    if(_now_taskReq.length<=0){
        var _warning_msg="request helper warning:task list has less than 1 task,do not send any request now.";
        logger.warn(_warning_msg);
        if(onDone){
            onDone();
        }
        return;
    }
    Q.all(_now_taskReq)
        .spread(function(){}).done(function(){
            if(onDone){
                onDone();
            }
        });

    };
module.exports=RequestHelper;

どう使いますか
//    
router.get('/recommend',function(req,res,next){
    GlobalService.getCommonPageData(req,function(commonData){
        //res.send(JSON.stringify(commonData));

        var pageData={
            list:[]
            ,ads:[]
        };

        var ajaxHelper=new RequestHepler();
        ajaxHelper.addTask({
            url:ServerConf.ApiHost+Settings.WebApi.RecommendProductApi
            ,success:function(data){

                    var _json=data;
                    pageData.list=_json.rows;

            }
            ,error:function(error){
                logger.error(error);
            }
        });
        ajaxHelper.addTask({
            url:ServerConf.ApiHost+Settings.WebApi.RecommendProductAdApi
            ,success:function(data){

                    var _json=data;
                    pageData.ads=_json.rows;
            }
            ,error:function(error){
                logger.error(error);
            }
        });
        ajaxHelper.run(function(){
            commonData["pageData"]=pageData;
            res.render('recommend', commonData);
        });

    });
});
原理は複数のタスクのキューに入れて、最後に最後の方法を実行することです.