Использование обещаний/обратных вызовов для ожидания завершения функции в javascript

У меня есть функция, которая создает объект базы данных из трех массивов. Массивы заполняются в каждом цикле, один из массивов опирается на значение в той же итерации цикла.

Зависимый массив использует библиотеку requests и cheerio, чтобы получить строку для заполнения массива.

В настоящее время зависимый массив заполняется нулями, что, я думаю, связано с тем, что цикл не ожидает возврата запроса.

Я все еще учусь и хотел бы, чтобы это работало без прямой блокировки, чтобы сохранять асинхронность, поэтому я изучаю обещания/обратные вызовы.

Это делается на стороне сервера, но из того, что я видел в документах cheerios, возможности промисов нет.

Вот что у меня есть до сих пор. (getFile() — это функция, которая не заполняет массив «c», она также зависит от текущего значения, помещаемого в «b»). Я знаю, что функция getFile получает правильное значение с помощью теста журнала консоли, поэтому проблема должна быть в реализации заполнения «c».

addToDB() - это функция, которая сохраняет значение в mongoDB, из тестирования я знаю, что объекты правильно помещаются в базу данных, просто массив c неверен.

function getInfo(path) {
  $(path).each(function(i,e) {
    a.push(...)
    b.push(value)
    c.push(getFile(value))
  })
  var entry = new DB...//(a,b,c)
  addToDB(entry);
}

function getFile(...) {
  request(fullUrl, function (err, resp, page) {
    if (!err && resp.statusCode == 200) {
      var $ = cheerio.load(page); // load the page
      srcEp = $(this).attr("src");
      return srcEp;
    } // end error and status code
  }); // end request
}

Я читал об обещаниях/обратных вызовах, а затем(), но я еще не нашел ничего, что работает.


person user147910    schedule 08.08.2015    source источник
comment
Я предполагаю, что addToDB() - это ваша асинхронная операция. Если это так, то чтобы мы могли помочь вам решить эту проблему, вам нужно будет показать этот код. Вот где должны произойти настоящие изменения.   -  person jfriend00    schedule 08.08.2015
comment
addToDB — это просто функция, которая берет заполненные массивы и сохраняет их. Таким образом, он будет иметь: var objectToMake = new... а затем сохранение ниже будет (через mongodb) objectToMake.save, асинхронный, который я считаю (все еще изучаю js, не уверен, что это проблема) - это массив c не заполняется, то есть каждый цикл не ждет завершения getFile   -  person user147910    schedule 08.08.2015
comment
Где ваша асинхронная операция? Что вы ждете, чтобы закончить? Ваш вопрос говорит об ожидании завершения чего-то, но не говорит, какую операцию ждать (и показывает код для этой операции). Обещания помогают решить проблему, только если у вас есть асинхронная операция. В противном случае синхронные операции просто выполняются по порядку.   -  person jfriend00    schedule 08.08.2015
comment
Выполняет ли getFile() асинхронный вызов? Можете показать, как это выглядит? Если он асинхронный, он не может вернуть такое значение, но может вернуть обещание.   -  person John S    schedule 08.08.2015
comment
Я должен был уточнить, функция getFile делает запрос и возвращает результаты   -  person user147910    schedule 08.08.2015
comment
для getFile он использует значение из массива b на текущей итерации цикла, чтобы сделать запрос, и использует cheerio для анализа строки, которую я хочу заполнить массивом c   -  person user147910    schedule 08.08.2015
comment
Затем покажите нам код getFile(). Вы, кажется, не понимаете, что НЕ показали наиболее важную часть кода - часть, которая где-то делает асинхронный запрос. Мы не сможем вам помочь, пока вы не опишете и не раскроете асинхронную часть своего кода.   -  person jfriend00    schedule 08.08.2015
comment
И, если вы не понимаете, какие из ваших операций синхронные, а какие асинхронные (или даже в чем разница между этими двумя типами операций), то это будет место для начала, потому что ни одно из этих действий не может быть решено до тех пор, пока понятно. Сохранение в удаленной базе данных или получение удаленного файла (в основном любая операция, связанная с сетью), скорее всего, является асинхронной.   -  person jfriend00    schedule 08.08.2015
comment
Вот что делает функция getFile: request(fullUrl, function (err, resp, page) { if (!err && resp.statusCode == 200) { var $ = cheerio.load(page); // загружаем страницу srcEp = $(this).attr(src);return srcEp;} // конец ошибки и код состояния }); // конец запроса   -  person user147910    schedule 08.08.2015
comment
Пожалуйста, используйте ссылку редактирования под вашим вопросом, чтобы ДОБАВИТЬ этот код getFile() к вашему вопросу. Это очень актуально для вашего вопроса и требуется, чтобы люди понимали, что вы делаете. Я также предположил бы, что функция addToDB() также может иметь асинхронный компонент.   -  person jfriend00    schedule 08.08.2015
comment
Обнаружу ли я там jQuery?   -  person Roamer-1888    schedule 10.08.2015
comment
Я изучил jQuery, но он работает на стороне сервера, но я использую библиотеку cheerio, которая имеет некоторую базовую реализацию jQuery для стороны сервера. Однако я не нашел никаких отложенных или обещаний. cheerio. Я изучал bluebird, и мне кажется, что у него может быть потенциал, но я не уверен, как он работает еще.   -  person user147910    schedule 10.08.2015
comment
Является ли paths массивом js или объектом cheero?   -  person Roamer-1888    schedule 10.08.2015
comment
Это строка csspath, которую я использую для загрузки cheerio как: $ = cheerio.load   -  person user147910    schedule 10.08.2015
comment
Хорошо, я не использовал Cheerio раньше. Думаю, теперь я понял. Ответ в процессе подготовки...   -  person Roamer-1888    schedule 10.08.2015


Ответы (1)


Во-первых, вы должны осознать тот факт, что любой процесс, который хотя бы частично зависит от асинхронного подпроцесса, сам по себе является асинхронным.

На самом низком уровне кода этого вопроса request() является асинхронным, поэтому его вызывающий объект getFile() является асинхронным, а его вызывающий объект getInfo() также асинхронным.

Обещания являются абстракцией результатов асинхронных процессов и очень помогают в кодировании действий, которые необходимо предпринять, когда такие процессы завершатся — успешно или при неудаче.

Как правило, низкоуровневые асинхронные функции должны возвращать обещание для выполнения действия вызывающими их объектами, которые, в свою очередь, возвращают обещание своим вызывающим объектам и так далее вверх по стеку вызовов. Внутри каждой функции возвращаемые промисы могут обрабатываться с использованием методов промисов, в основном .then(), и могут быть агрегированы с использованием, например, Promise.all() (синтаксис различается).

В этом вопросе нет доказательств того, что request() в настоящее время возвращает обещание. У вас есть три варианта:

  • обнаружить, что request() действительно возвращает обещание.
  • перепишите request(), чтобы вернуть обещание.
  • напишите функцию-адаптер («обещатель»), которая вызывает request() и генерирует/возвращает обещание, которое позже выполняется или отклоняется в зависимости от результата request().

Первый или второй варианты были бы идеальными, но для меня (Roamer) безопасным предположением является предположение, что адаптер требуется. К счастью, я знаю достаточно из вопроса, чтобы написать его. Cheerio, по-видимому, не включает реализацию промисов jQuery, поэтому потребуется специальная библиотека промисов.

Вот функция адаптера, использующая синтаксис, который будет работать с промисами библиотеки Bluebird или нативного js:

//Promisifier for the low level function request()
function requestAsync(url) {
    return new Promise(function(resolve, reject) {
        request(url, function(err, resp, page) {
            if (err) {
                reject(err);
            } else {
                if (resp.statusCode !== 200) {
                    reject(new Error('request error: ' + resp.statusCode));
                }
            } else {
                resolve(page);
            }
        });
    });
}

Теперь можно написать getFile(...) и getInfo(), чтобы использовать промисы, возвращаемые адаптером самого низкого уровня.

//intermediate level function
function getFile(DOMelement) {
    var fullUrl = ...;//something derived from DOMelement. Presumably either .val() or .text()
    return requestAsync(fullUrl).then(function (page) {
        var $ = cheerio.load(page);
        srcEp = $(???).attr('src');//Not too sure what the selector should be. `$(this)` definitely won't work.
        return srcEp;
    });
}

//high level function
function getInfo(path) {
    var a = [], b = [], c = [];//presumably

    // Now map the $(path) to an array of promises by calling getFile() inside a .map() callback.
    // By chaining .then() a/b/c are populated when the async data arrives. 
    var promises = $(path).map(function(i, e) {
        return getFile(e).then(function(srcEp) {
            a[i] = ...;
            b[i] = e;
            c[i] = srcEp;
        });
    });
    //return an aggregated promise to getInfo's caller,
    //in case it needs to take any action on settlement.
    return Promise.all(promises).then(function() {
        //What to do when all promises are fulfilled
        var entry = new DB...//(a,b,c)
        addToDB(entry);
    }, function(error) {
        //What to do if any of the promises fails
        console.log(error);
        //... you may want to do more.
    });
}
person Roamer-1888    schedule 10.08.2015