Почему моя цепочка обещаний не запускается последовательно, когда я использую экспортированный модуль, а работает, когда я не экспортирую модуль? - PullRequest
0 голосов
/ 18 февраля 2020

Я пытаюсь получить данные в виде csv из URL-адреса в одном файле, а затем преобразовать этот csv в другом файле javascript в объект JSON, используя цепочку обещаний. Проблема в том, что он пытается преобразовать csv в json до того, как обратный вызов фактически извлечет csv.
(Примечание: я понимаю, что использую библиотеку запросов, которая сейчас устарела, и изменит ее, как только у меня цепочка обещаний работает).

Файл 1 (получение данных csv из URL) находится ниже. Мне нужно опросить URL, пока он не заполнится данными (обычно это занимает около 5 минут - заранее есть шаги аутентификации, но они работают нормально, поэтому я опускаю их, чтобы упростить это.) Это возвращает обещание, которое экспортируется как get_csv (). Обещание разрешается для строки CSV.

const request = require('request');

const csv_url = foo;

module.exports.get_csv = () => {
  return new Promise ((resolve, reject) => {
    function poll_url () {
      request.get({url: csv_url
      }, (error, response, body) => {
        if (error) console.error(error);
        data_csv = body;
        if (data_csv === ''){                             
          console.log("data_csv is blank.");
        } else {
          clearInterval(intervalID);
          console.log("data_csv has been populated!");
          resolve(data_csv);
        }
      })                        
    }                           

    pollURL()
    var intervalID = setInterval(poll_url, 60000);
  })                            
};

JS файл 2 (изменение данных CSV на json) ниже. Это означает просто добавить .then () к обещанию из файла 1, который преобразует данные из csv в json, а затем console.log () в данные json.

const data = require('./get_csv_data');   // File 1
const csv = require('csvtojson');

new Promise ((resolve, reject) => {
  const data_csv_string = data.get_csv()
  resolve(data_csv_string);
})

.then(
  (data_csv_string) => {
    console.log(data_csv_string);                        // for debugging - giving undefined
    csv({output: "json"}).fromString(data_csv_string)    // "Cannot read property 'toString' of undefined"
    .then((data_json_string) => {
      console.log(data_json_string);
    })
  }
)

Проблема заключается в том, что .then () файла 2 не ожидает завершения обратного вызова первого обещания. Консоль распечатает:

undefined
(node:10691) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'toString' of undefined
data_csv is blank.
data_csv is blank.
data_csv is blank.
data_csv is blank.
data_csv is blank.
data_csv is blank.
data_csv has been populated!

То, что я не могу обернуть вокруг себя, это то, что, если я добавлю условие .then () ниже в конце файла 1, оно будет работать. Что я делаю не так?

.then(
  (data_csv_string) => {
    console.log(data_csv_string);                        
    csv({output: "json"}).fromString(data_csv_string)    
    .then((data_json_string) => {
      console.log(data_json_string);
    })
  }
)

Ответы [ 3 ]

0 голосов
/ 18 февраля 2020

Этот блок

new Promise ((resolve, reject) => {
  const data_csv_string = data.get_csv()
  resolve(data_csv_string);
})
.then(

неправильный. Если вы хотите заключить что-то (возможно, обещание, возможно, значение) в обещание, выполните

Promise.resolve(data.get_csv())
    .then(//...

Но, поскольку вы знаете, что data.get_csv() уже возвращает обещание, в этом нет необходимости. Просто замените конструктор Promise на

data.get_csv()
    .then(//...

И, между прочим, есть утилита Node для многообещающих функций на основе обратного вызова: util.promisify.

0 голосов
/ 18 февраля 2020

setInterval следует избегать для таких случаев использования, как это, потому что в некоторых случаях множественные вызовы обратного вызова могут быть поставлены в очередь, а затем удалены из режима «скорострельность». Лучше явно вызывать попытки повтора, когда вы знаете, что готовы; это дает вам больше контроля.

Существует ряд проблем с вашим кодом.

Например:

pollURL()
var intervalID = setInterval(poll_url, 60000)

Эти строки запускают опрос, даже если первая попытка прошла успешно, что может быть не тем, что вы хотите.

Вот рефакторинг (непроверенный, очевидно):

const request = require('request')

const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

const repeatUntil = ({fn, condition, interval}) => {
    const go = async (...args) => {
        const result = await fn(...args)
        if(condition(result)) return result
        else {
            await delay(interval)
            await go(...args)
        }
    }
    return go
}

const getURL = (url) => new Promise((resolve, reject) => 
    request.get({url}, (error, response, body) => {
        if (error) return reject(error)
        resolve({response, body})
    }))  

const csv_url = 'http://example.com'
const condition = ({body}) => !!body
const interval = 60000
const getCSV = repeatUntil({ fn: () => getURL(csv_url), condition, interval })

module.exports.getCSV = getCSV

Файл 1

const getCSV = require('./get-csv')   // File 1
const csvToJSON = require('csvtojson')

getCSV()
    .then(({body}) => {
        const json = csvToJSON({output: "json"}).fromString(body) 
        console.log(json)
    })

0 голосов
/ 18 февраля 2020

Файл 2 должен быть:

const data = require('./get_csv_data');   // File 1
const csv = require('csvtojson');

data.get_csv().then(
  (data_csv_string) => {
    console.log(data_csv_string);                        // for debugging - giving undefined
    csv({output: "json"}).fromString(data_csv_string)    // "Cannot read property 'toString' of undefined"
    .then((data_json_string) => {
      console.log(data_json_string);
    })
  }
)
...