对多个节点请求使用 Promise

Posted

技术标签:

【中文标题】对多个节点请求使用 Promise【英文标题】:Use promises for multiple node requests 【发布时间】: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 在哪里使用?为什么不处理任何错误? 【参考方案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);
);

【讨论】:

避免promise constructor antipattern【参考方案5】:

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

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

【讨论】:

以上是关于对多个节点请求使用 Promise的主要内容,如果未能解决你的问题,请参考以下文章

关于Promise详解

Slurm 作业不能为多个节点请求 GPU 资源

如何使用 HTTP 请求触发 Firebase 函数以从多个节点读取数据?

js进阶五(js回调promisepromise嵌套异常处理jquery使用promise)

redis 集群

redis 集群