Есть ли способ заполнить объект JSON до разрешения обещания? - PullRequest
0 голосов
/ 18 июня 2019

код сначала получает все URL из базы данных.и в parseText я пытаюсь проанализировать все ur и поместить их в объект Json для дальнейшего использования.

я попытался запустить цикл for с помощью async / await, но это не дало мне ожидаемого результата.

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: {текст: некоторый проанализированный текст}}, но все, что я продолжаю получать, это пустой объект

1 Ответ

2 голосов
/ 18 июня 2019

В вашем коде есть несколько вещей, которые нужно решить, поэтому давайте пойдем шаг за шагом:

  • 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);
}

Надеюсь, комментарии помогут. Опять же, работа с обещаниями в цикле - отстой.

Есть два способа сделать это более простым способом! оба используют функциональные методы массивов. Первый самый простой, и я рекомендую его, если массив не будет очень большим. Он использует .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);
}

Примечание: 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 - цепочка, просто используя .reduce вместо ручной for петли.

Обратите внимание, что лично мне не обязательно делать это, так как я думаю, что это слишком "умно" и требует умственного разбора. Если реализация чего-то подобного (then -цепь с использованием .reduce является невероятно полезной, даже если немного запутанной), пожалуйста, добавьте комментарий, объясняющий, почему вы это сделали, что это такое или что-то, что может помочь другие разработчики понимают их с первого взгляда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...