Node.js 将异步转换为承诺

Node.js converting async into promise

我有一个中间件函数,它命中一个 api 和 returns 一个 ID。这工作正常,但在 returns 之前用数据调用 next。第一次渲染视图时没有数据。我更愿意将其包装在一个承诺中,并在可用时更新视图。实现这个的最佳方法是什么。我一直在努力用 Q 服务实现一些东西,但没有任何运气。

Server.js

var app        = express();
var guid       = require('./server/guid.js')(app);

app.use(guid.checkGUID);

guid.js

var express    = require('express');
var Q          = require('q');
var request    = require('request');
var uuid       = require('uuid');

module.exports = function(app){


var checkGUID = function(req, res, next) {
  if(!app.locals.guid){
    getGUID();
    next();
  }else{
    next();
  }
};

var getGUIDOptions = {
  options....
};

var getGUID = function(req,res, next) {
  request(getGUIDOptions, returnGUID);  
};

function returnGUID(error, response, body) {
  if (!error && response.statusCode == 200) {
    app.locals.guid = body.guid;
    return body.guid;
  }else{
    console.log(body);
  }
};

return {
        checkGUID: checkGUID
    } 
}

不需要使用Promises,只需要为请求调用中间件next()回调即可。这是我的写法,请注意,我尝试正确处理所有预期的错误情况。

var util=require('util'),
    request = require('request');

module.exports = function(app) {

  /**
   * Assure we have an app-wide GUID
   * @param {object} req
   * @param {object} res
   * @param {function} next - callback to next stage in request processing
   */
  var assureGUID = function(req, res, next) {
    if(app.locals.guid){ // we already have one
      next();
      return;
    }
    getGUID(req, res, next);
  };

  /**
   * Retrieve a GUID for this app
   * @param {object} req
   * @param {object} res
   * @param {function} next - callback to next stage in request processing
   */
  var getGUID = function(req, res, next) {

    var getGUIDOptions = {
      //options....
    };

    request(getGUIDOptions, function(err,resp,body){
      if(err){
        next(err);
        return;
      }
      if(200 !== resp.statusCode){
        next(new Error('failed to retrieve GUID, status='+resp.statusCode));
        return;
      }
      if(!('object'===typeof body && 'string'===typeof body.guid && body.guid)){
        next(new Error('failed to retrieve GUID, resp: '+util.inspect(resp,{depth:null})));
        return;
      }

      app.locals.guid=body.guid;

      next();
    });
  };

  return {
    assureGUID: assureGUID
  }
};

本质上,当请求到达此处理程序并且我们已经有了此应用程序的 GUID 时,我们继续(通过调用 next())。如果我们还没有 GUID,则会请求一个新的 GUID,当我们拥有它时,我们调用 next()。

如果您对使用 Promises 束手无策,我也很乐意附加该解决方案。

更新

好的,这是承诺的(原文如此)Promises 版本。我(和许多其他人)更喜欢 Petka Antonov 的 bluebird Promises 实现,所以我将在此处使用它而不是 Q,如果需要,您应该能够毫不费力地将此代码转换为。

// Promises version

var util=require('util'),
    Promise=require('bluebird'),
    request = Promise.promisifyAll(require('request')); // see note 1.

var assureGUID = function(req,res,next){

  if(app.locals.guid){ // we already have one
    next();
    return;
  }

  var getGUIDOptions = {
    //options....
  };

  request.get(getGUIDOptions) // see note 2.
      .catch(function(err){ // get request failed
        next(new Error('GET request for GUID failed:'+err));
      })
      .spread(function(resp,body){ // see note 3.
        // check for proper result
        if(200 !== resp.statusCode){
          throw new Error('failed to retrieve GUID, status='+resp.statusCode);
        }
        if(!('object'===typeof body && 'string'===typeof body.guid && body.guid)){
          throw new Error('failed to retrieve GUID, resp: '+util.inspect(resp,{depth:null}));
        }
        app.locals.guid=body.guid;
        next();
      })
      .catch(function(err){ // unexpected result
        next(err);
      });
};

一些注意事项:

  1. Promise.promisifyAll() 自动request 模块公开的所有函数转换为 Promise,如果它们遵循标准 f(err,res) 节点回调模式。
  2. 请参阅 bluebird 中关于 "promisification" 的注释,了解我们为什么需要使用 request.get() 而不是简单地使用 request()
  3. 如果调用不支持 f(err,res),您可以使用 Promise.spread() 到 "spread" 基本函数的可用参数。因此,由于 request.get() 是用三个参数调用的:err(由 Promise 使用)、resbody,我们需要 "spread" 剩余的两个参数进入新的 Promise 回调。

最后,虽然我没有测试过这段代码,但它应该可以工作。如果您有任何问题,请随时添加更多评论。