对多个节点请求使用 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');
);
);
【问题讨论】:
someURL
和anotherURL
从哪里来,第二个请求真的依赖第一个吗?另外,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
以刷新该数据。
注意事项:
这将并行执行contextA
和contextB
,但当A
和B
都被解析时,将执行需要这两个上下文的最终计算。
这是我的新尝试。
上述解决方案的主要问题是没有一个 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的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 HTTP 请求触发 Firebase 函数以从多个节点读取数据?