有没有办法在解决诺言之前填充JSON对象

时间:2019-06-17 23:18:57

标签: javascript json for-loop promise

代码首先从数据库中获取所有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}},但我一直得到的只是一个空对象

1 个答案:

答案 0 :(得分:2)

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

    dbresult的使用来看,
  • dbresult[i]似乎是一个数组,但是您还具有一个i < dbresult条件,表明它是整数。我假设你的意思是i < dbresult.length
  • 您已经在处理诺言的情况下使用new Promise(...)。除非没有其他选择,否则您应该从不使用此模式,并始终尝试链接.then调用并返回其结果(始终也是承诺)。
  • 您似乎无法理解,在其余代码运行之后,传递给.then的回调将始终异步运行。这就是您的对象变空的原因:在任何请求都没有时间完成之前,将调用resolve函数。

现在,循环和承诺并不能很好地融合在一起,但是有一些处理方式。您需要理解的是,使用循环,链式承诺。以这种方式链接诺言的方法主要有两种:命令式和功能式。

我将专注于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);
}

我希望这些评论有所帮助。再次,循环处理承诺很糟糕。

不过,有两种方法可以更轻松地完成此操作!两者都使用数组的功能方法。第一个是最简单的,它是我推荐的一个,除非数组很大。它利用了两个强大的盟友.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并完全忘记承诺。在这种情况下,您可以正常编写循环,并在适当的地方简单地等待:

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链接非常有用,即使有些混乱,即使有一点混乱也请使用),然后添加一条注释,说明您这样做的原因,含义,或可以帮助其他开发人员乍一看的东西。