【问题标题】:Use promises for multiple node requests对多个节点请求使用 Promise
【发布时间】:2015-12-04 11:40:37
【问题描述】:

有了request 库,有没有办法使用promise 来简化这个回调?

  var context = {};

  request.get({
    url: someURL,
  }, function(err, response, body) {

    context.one = JSON.parse(body);

    request.get({
      url: anotherURL,
    }, function(err, response, body) {
      context.two = JSON.parse(body);

      // render page
      res.render('pages/myPage');
    });
  });

【问题讨论】:

  • someURLanotherURL从哪里来,第二个请求真的依赖第一个吗?另外,context 在哪里使用?为什么不处理任何错误?

标签: node.js express promise


【解决方案1】:

您可以使用request-promise 库来执行此操作。在你的情况下,你可以有这样的东西,你可以在其中链接你的请求。

request
    .get({ url: someURL })
    .then(body => {
        context.one = JSON.parse(body);

        // Resolves the promise
        return request.get({ url: anotherURL });
    })
    .then(body => {
        context.two = JSON.parse(body);
        res.render('pages/myPage');
    })
    .catch(e => {
        //Catch errors
        console.log('Error:', e);
    });

【讨论】:

    【解决方案2】:

    这是我将如何实现链式 Promises。

    var request = require("request");
    
    var someURL = 'http://ip.jsontest.com/';
    var anotherURL = 'http://ip.jsontest.com/';
    
    function combinePromises(context){
      return Promise.all(
        [someURL, anotherURL].map((url, i)=> {
    
          return new Promise(function(resolve, reject){
    
            try{
    
              request.get({
                url: url,
              }, function(err, response, body) {
    
                if(err){
                  reject(err);
                }else{
                  context[i+1] = JSON.parse(body);
                  resolve(1); //you can send back anything you want here
                }
    
              });
    
            }catch(error){
              reject(error);
            }
    
          });
    
        })
      );
    }
    
    var context = {"1": "", "2": ""};
    combinePromises(context)
    .then(function(response){
    
      console.log(context);
      //render page
      res.render('pages/myPage');
      
    }, function(error){
      //do something with error here
    });

    【讨论】:

      【解决方案3】:

      这是使用 Bluebird Promise 库的解决方案。这会将两个请求序列化并将结果累积在context 对象中,并将错误处理全部汇总到一个位置:

      var Promise = require("bluebird");
      var request = Promise.promisifyAll(require("request"), {multiArgs: true});
      
      var context = {};
      request.getAsync(someURL).spread(function(response, body) {
          context.one = JSON.parse(body);
          return request.getAsync(anotherURL);
      }).spread(response, body)
          context.two = JSON.parse(body);
          // render page
          res.render('pages/myPage');
      }).catch(function(err) {
          // error here
      });
      

      而且,如果您有多个 URL,您可以使用 Bluebirds 的一些其他功能,例如 Promise.map() 来迭代 URL 数组:

      var Promise = require("bluebird");
      var request = Promise.promisifyAll(require("request"), {multiArgs: true});
      
      var urlList = ["url1", "url2", "url3"];
      Promise.map(urlList, function(url) {
          return request.getAsync(url).spread(function(response,body) {
              return [JSON.parse(body),url];
          });
      }).then(function(results) {
           // results is an array of all the parsed bodies in order
      }).catch(function(err) {
           // handle error here
      });
      

      或者,您可以创建一个辅助函数来为您执行此操作:

      // pass an array of URLs
      function getBodies(array) {
          return Promise.map(urlList, function(url) {
              return request.getAsync(url).spread(function(response.body) {
                  return JSON.parse(body);
              });
          });
      });
      
      // sample usage of helper function
      getBodies(["url1", "url2", "url3"]).then(function(results) {
          // process results array here
      }).catch(function(err) {
          // process error here
      });
      

      【讨论】:

      • 添加了更多示例。
      • @userpassword - 我必须修正你的编辑。我不确定您为什么将我的答案中的蓝鸟库从Promise 更改为bluebird,它在我的代码中被声明为bluebird。我确实接受了您添加的 multiArgs,这是 Bluebird 在 3.0 中所做的更改。
      【解决方案4】:

      使用本机 Promises 执行此操作。了解一下胆量是件好事。

      这里被称为“Promise Constructor Antipattern”,正如@Bergi 在 cmets 中指出的那样。不要这样做。看看下面更好的方法。

      var contextA = new Promise(function(resolve, reject) {
        request('http://someurl.com', function(err, response, body) {
          if(err) reject(err);
          else {
            resolve(body.toJSON());
          }
        });
      });
      
      var contextB = new Promise(function(resolve, reject) {
        request('http://contextB.com', function(err, response, contextB) {
          if(err) reject(err);
          else {
            contextA.then(function(contextA) {
               res.render('page', contextA, contextB);
            });
          }
        });
      });
      

      这里的绝妙技巧,我认为通过使用原始承诺,您会意识到这一点,contextA 解析一次,然后我们就可以访问它的解析结果。也就是说,我们从不someurl.com 发出上述请求,但仍然可以访问contextA 的JSON。

      所以我可以想象创建一个contextC 并重用 JSON,而无需发出另一个请求。 Promises 总是只解析一次。您必须取出匿名 executor 函数并将其放入 new Promise 以刷新该数据。

      注意事项:

      这将并行执行contextAcontextB,但当AB 都被解析时,将执行需要这两个上下文的最终计算。

      这是我的新尝试。

      上述解决方案的主要问题是没有一个 Promise 是可重用的,并且它们不是链式,这是 Promises 的一个关键特性。

      但是,我仍然建议您自己使用 promisifying 您的 request 库,并避免向您的项目添加其他依赖项。 promisifying 自己的另一个好处是您可以编写自己的 rejection 逻辑。如果您正在使用在正文中发送 error 消息的特定 API,这一点很重要。一起来看看吧:

      //Function that returns a new Promise. Beats out constructor anti-pattern.
      const asyncReq = function(options) { 
       return new Promise(function (resolve, reject) {
        request(options, function(err, response, body) {
         //Rejected promises can be dealt with in a `catch` block.
         if(err) {
          return reject(err);
         } 
         //custom error handling logic for your application.
         else if (hasError(body)) {
          return reject(toError(body));
         }
         // typically I just `resolve` `res` since it contains `body`.
         return resolve(res);
        }
       });
      };
      
      asyncReq(urlA)
      .then(function(resA) {
        //Promise.all is the preferred method for managing nested context.
        return Promise.all([resA, asyncReq(urlB)]);
      })
      .then(function(resAB) {
        return render('page', resAB[0], resAB[1]);
      })
      .catch(function(e) {
        console.err(e);
      });
      

      【讨论】:

      【解决方案5】:

      到目前为止,最简单的方法是使用request-promise 库。您也可以使用类似 bluebird 的 promise 库并使用其 promisify 函数将 request 回调 API 转换为 promise API,尽管您可能需要编写自己的 promisify 函数,因为 request 不使用标准回调语义。最后,您可以使用原生 Promise 或 bluebird 制作自己的 Promise 包装器。

      如果您刚开始,只需使用 request-promise。如果您要重构现有代码,我会使用 bluebird 的 spread 函数为 request 编写一个简单的包装器。

      【讨论】:

        猜你喜欢
        • 2019-08-22
        • 1970-01-01
        • 1970-01-01
        • 2016-02-14
        • 1970-01-01
        • 1970-01-01
        • 2017-08-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多