【问题标题】:Is there a way to fill the JSON object before resolving the promise有没有办法在解决承诺之前填充 JSON 对象
【发布时间】:2019-06-17 23:18:57
【问题描述】:

代码首先从数据库中获取所有 url。 在 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 是什么数据类型?我很确定你搞砸了 for 循环的终止条件。应该是i &lt; dbresult.length

标签: javascript json for-loop promise


【解决方案1】:

您的代码中有很多事情需要处理,所以让我们一步一步来:

  • 从您对dbresult[i] 的使用来看,dbresult 似乎是一个数组,但您也有一个i &lt; dbresult 条件,这意味着它是一个整数。我会假设你的意思是i &lt; dbresult.length
  • 您正在使用new Promise(...) 在您已经处理承诺的情况下。你应该永远使用这种模式,除非你别无选择,并且总是尝试链接 .then 调用并返回它们的结果(这也是承诺)。
  • 您似乎没有理解传递给.then 的回调将始终异步运行,在其余代码运行之后。这就是您的对象为空的原因:在任何请求有时间完成之前调用了 resolve 函数。

现在,循环和 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);
}

我希望 cmets 有所帮助。同样,在循环中处理 Promise 很糟糕。

不过,有两种更简单的方法!两者都使用数组的函数方法。第一个是最简单的,除非阵列非常大,否则我会推荐它。它利用了.mapPromise.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);
}

注意:bluebird 用户可以使用Promise.map 并传递一个concurrency 值来使这一点变得更好。在我看来,这实际上是最好的解决方案,但我想在这里坚持使用香草。

不过,此解决方案的主要问题是所有请求都将立即启动。这可能意味着,对于非常大的数组,一些请求只是在队列中等待,或者您耗尽了进程的套接字限制,具体取决于实现。无论如何,这并不理想,但在大多数情况下都可以。

另一种功能性解决方案包括使用.reduce 而不是for ... of 循环复制命令式解决方案,并且它在答案的末尾实现,更多的是出于好奇而不是其他任何事情,因为我认为它也有点“聪明的代码”。

在我看来,解决此问题的最佳方法是只使用 async/await 而完全忘记承诺。在这种情况下,您可以正常编写循环,只需将 await 放在适当的位置:

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-chaining 的作用完全相同,只是使用.reduce 而不是手动的for 循环。

请注意,我个人不一定会这样做,因为我认为这有点过于“聪明”,需要一些时间来进行心理分析。如果实现这样的事情(then-chaining 使用.reduce令人难以置信有用,即使有点混乱)请添加评论解释你为什么这样做,它是什么,或者可以帮助其他开发者第一眼了解 ir。

【讨论】:

  • "...我不打算写它,因为它“聪明”得离谱,甚至胜过更有经验的开发人员” 现在你让我很好奇,大家意思是请张贴。 +1 顺便说一句,写得很好。
  • @zer00ne 你去。也许它没有我想象的那么聪明,但我前段时间在调试某些东西时读到了类似的东西,我花了一段时间才明白到底发生了什么。
  • 非常感谢,.length 我没有马上看到,但这不是问题。由于这个解释,我现在设法继续甚至完成了代码。非常感谢
猜你喜欢
  • 2017-11-24
  • 1970-01-01
  • 2015-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-20
  • 2019-10-18
相关资源
最近更新 更多