我正在学习如何使用Promises。我有以下函数将“i”th xkcd漫画标题作为Promise返回:
var xkcd = function(i) {
return new Promise(
function(resolve, reject) {
var tempurl = 'https://www.xkcd.com/' + i;
request(tempurl, function(error, response, body) {
if (error) reject(error);
var $ = cheerio.load(body);
resolve($('title').text() + '\n');
});
});
};
如果我想获得前4个标题,我就是这样链接我的.then():
var result = '';
xkcd(1)
.then(fullfilled => {
result += fullfilled;
})
.then(() => xkcd(2))
.then(fullfilled => {
result += fullfilled;
})
.then(() => xkcd(3))
.then(fullfilled => {
result += fullfilled;
})
.then(() => xkcd(4))
.then(fullfilled => {
result += fullfilled;
console.log(result);
});
有没有更优雅的方式来做这个而不用链接这么多“那么”?如果我想获得前50部漫画,我将不得不连结很多“当时”。
我可以使用递归回调而不使用Promise来实现:
function getXKCD(n) {
var i = 1;
(function getURL(i){
var tempurl = 'https://www.xkcd.com/' + i;
request(tempurl, function(error, response, body) {
if (error) console.log('error: ' + error);
var $ = cheerio.load(body);
//prints the title of the xkcd comic
console.log($('title').text() + '\n');
i++;
if (i <= n) getURL(i);
});
})(i);
}
getXKCD(4);
但我很想知道我是否可以对Promises做同样的事情。谢谢。
答案 0 :(得分:4)
你可以将promises推送到一个数组,然后返回Promise.all
,这将解决所有promises已经解决的问题,例如
function getXKCD(_start, _end) {
if (_end >= _start) return Promise.reject('Not valid!');
var promises = [];
(function rec(i) {
var p = new Promise(function(resolve, reject) {
request('https://www.xkcd.com/' + i, function(error, response, body) {
if (error !== null) return reject(error);
if (i <= _end) rec(++i);
let $ = cheerio.load(body);
resolve($('title').text());
});
});
promises.push(p);
})(_start);
return Promise.all(promises);
}
getXKCD(1, 50).then(res => { /* All done ! */ }).catch( err => { /* fail */ })
答案 1 :(得分:3)
如果您想按顺序获取文章:
function getSequentially(currentArticle, doUntil) {
if (currentArticle !== doUntil) {
xkcd(currentArticle)
.then(article => getSequentially(currentArtile + 1, doUntil))
}
}
如果您想立即获取所有文章:
Promise
.all(new Array(AMOUNT_OF_ARTICLES).fill(null).map((nll, i) => xkcd(i + 1)))
.then(allArticles => ...);
我不会假装上述所有内容在复制/粘贴后都能正常工作,这只是了解如何执行此操作。
答案 2 :(得分:1)
有几种方法。您可以使用所需的所有值填充数组,然后使用.map()
Promise.all()
或.reduce()
,以便它们按顺序发生:
function getXkcd(count) {
// Make an array of the comic numbers using ES6 Array.fill()...
var ids = new Array(count).fill(1).map((val, index)=>index+1)
// Now you can .map() or .reduce() over this list.
}
还有其他有趣的方法可以解决这个问题。您可以使用递归包装器来完成工作。保留原始xkcd
函数,您可以构建一个递归调用自身的简单函数...
function getXkcd(max, last) {
var current = last ? last + 1 : 1;
xkcd(current)
.then(function(title) {
// Process the data.
result += title;
// We don't really care about a return value, here.
})
.then(getXkcd.bind(null, max, current))
.catch(function(error) {
// We should do something to let you know if stopped.
});
}
这非常接近你的回调版本。唯一真正的区别是我们使用bind
来传递当前值和最大值而不是闭包。这确实有一个好处,它从中间开始自动处理:getXkcd(50, 15);
这也可以添加到你的回调示例中。
使用闭包让我们保持状态并创建一个可能更清洁的递归调用:
function getXKCD(max, start) {
var result = "";
var getNext = function(id){
// If we are done, return the result
if (id > n) {
return result;
}
// Otherwise, keep going.
return xkcd(id)
.then(function(title){
// Accumulate the title in our closure result
result += title;
// Send next value
return id + 1;
})
.then(getNext);
}
// Kick off the loop
return getNext(start || 1);
}
getXKCD(50).then(function(results){
// Do something with the results
}, function(error){
// Tell us what went wrong
});
在getXKCD
内,我们创建了一个函数getNext
,它在Promise链的末尾调用自身。它的工作方式类似于reducer,序列化请求,并最终返回收集的结果。这个不使用bind,但接受链中上一步的“下一个值”。
答案 3 :(得分:0)
使用async-await
是最好的方式,IMO:
(async () => {
const result = '';
for(let i = 1; i < 50; i++) {
result += await xkcd(i);
}
return result
})().then(result => console.log(result))