代码首先从数据库获取所有网址。 在 parseText 中,我尝试解析所有 ur 并将它们放入 Json 对象中以供以后引用。
我尝试使用 async/await 运行 for 循环,但这并没有给我预期的结果。
let parseText = function(dbresult) {
return new Promise((resolve, reject) => {
let textObj = {}
for(i=0; i < dbresult; i++) {
Mercury.parse(dbresult[i].url, {headers: {Cookie: 'name=Bs', 'User-Agent': 'Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)', },})
.then((result) => {
textObj[dbresult[i].id] = result.excerpt;
});
}
resolve(textObj);
})
}
fetchLinks.then(function(result) {
return parseText(result);
}).then(function(result) {
console.log(result); //gives back {}
//do something with json object in next promise
return writeTextToDb(result); //not written yet
})
所需的输出应该类似于 {1234 : {text: some parsed text}},但我得到的只是一个空对象
最佳答案
您的代码中有很多事情需要处理,所以让我们一步一步来:
-
dbresult
从您对dbresult[i]
的使用来看,似乎是一个数组,但您还有i < dbresult
暗示它是整数的条件。我假设你的意思是i < dbresult.length
. - 您正在使用
new Promise(...)
在你已经在处理 promise 的情况下。除非您没有其他选择,否则您不应该使用此模式,并且始终尝试链接.then
调用并返回结果(这也始终是 promise )。 - 您似乎没有理解回调传递给
.then
在其余代码运行之后,将始终异步运行。这就是你的对象为空的原因:resolve
在任何请求有时间完成之前调用函数。
现在,循环和 promise 不能很好地混合,但是有一些方法可以处理它们。您需要理解的是,对于循环,您想要的是链式 promise 。以这种方式链接 Promise 的方式主要有两种:命令式方式和函数式方式。
我将重点关注 parseText
功能并省略不相关的细节。对于完全命令式解决方案,您将执行以下操作:
function parseText (dbresult) {
// although the contents of the object change, the object doesn't,
// so we can just use const here
const textObj = {};
// initialize this variable to a dummy promise
let promise = Promise.resolve();
// dbresult is an array, it's clearer to iterate this way
for (const result of dbresult) {
// after the current promise finishes, chain a .then and replace
// it with the returned promise. That will make every new iteration
// append a then after the last one.
promise = promise
.then(() => Mercury.parse(result.url, {...}))
.then((response) => (textObj[result.id] = response.excerpt));
}
// in the end, the promise stored in the promise variable will resolve
// after all of that has already happened. We just need to return the
// object we want to return and that's it.
return promise.then(() => textObj);
}
希望评论对您有所帮助。同样,在循环中使用 Promise 很糟糕。
不过,有两种更简单的方法可以做到这一点!两者都使用数组的函数方法。第一个是最简单的,也是我推荐的,除非数组非常大。它利用.map
和Promise.all
,两个强大的盟友:
function parseText (dbresult) {
const textObj = {};
// create an array with all the promises
const promises = dbresult.map(result => Mercury.parse(result.url, {...})
.then((response) => (textObj[result.id] = response.excerpt)))
);
// await for all of them, then return our desired object
return Promise.all(promises).then(() => textObj);
}
Note:
bluebird
users can make this even better usingPromise.map
and passing aconcurrency
value. That is actually, in my opinion, the best solution, but I want to stick with vanilla here.
不过,此解决方案的主要问题是所有请求都将立即启动。这可能意味着,对于非常大的数组,某些请求只是在队列中等待,或者耗尽进程的套接字限制,具体取决于实现。无论如何,这并不理想,但在大多数情况下都可以。
另一种功能解决方案包括使用 .reduce
复制命令式解决方案。而不是for ... of
循环,它是在答案的末尾实现的,更多的是出于好奇而不是其他任何事情,因为我认为这有点太“聪明的代码”。
在我看来,解决这个问题的最好方法就是使用 async/await
并完全忘记 promise 。在这种情况下,您可以正常编写循环,并在适当的地方简单地放置等待:
async function parseText (dbresult) {
const textObj = {};
for (const result of dbresult) {
// here just await the request, then do whatever with it
const response = await Mercury.parse(result.url, {...}))
textObj[result.id] = response.excerpt;
}
// thanks to await, here we already have the result we want
return textObj;
}
就是这样,就这么简单。
<小时/>现在,我认为这是“聪明”的解决方案,仅使用 .reduce
:
function parseText (dbresult) {
const textObj = {};
return dbresult.reduce(
(prom, result) => prom
.then(() => Mercury.parse(result.url, {...}))
.then((response) => (textObj[result.id] = response.excerpt)),
Promise.resolve()
).then(() => textObj);
}
如果不能立即清楚它的作用,那是正常的。这与原始命令 then
的作用完全相同。 - 链接一个即可,只需使用 .reduce
而不是手册for
循环。
请注意,我个人不一定会这样做,因为我认为这有点太“聪明”并且需要一些时间在心理上进行解析。如果实现这样的东西( then
-使用 .reduce
链接非常有用,即使有点令人困惑),请添加一条评论,解释你为什么这样做,它是什么,或者可以帮助其他开发者第一眼就理解 ir。
关于javascript - 有没有办法在解决 promise 之前填充 JSON 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56639950/