JavaScript Promise внутри функции async / await разрешает окончательный массив ответов - PullRequest
0 голосов
/ 22 ноября 2018

Я новичок в JavaScript и в Promises.Я пытаюсь создать массив объектов, которые я получаю из API.

Для этого я построил две функции в файле MyFile.js.

Первая возвращаетобещание, когда обещание аксиоса разрешено.Это

function get_items (url) {
    return new Promise((resolve, reject) => {
        let options = {
            baseURL: url,
            method: 'get'
        }
        axios(options)
            .then(response => {
                resolve(response.data)
            })
            .catch(error => {
                reject(error.stack)
            })
    })
}

Второй выглядит так:

let output = []
let next_url = 'https://some_url.com/api/data'
async function get_data () {
    try {
        let promise = new Promise((resolve, reject) => {
            if (next_url) {
                get_items(next_url)
                    .then(response => {
                        output.push(...response.results)
                        if (response.next) {
                            next_url = response.next
                            console.log('NEXT_URL HERE', next_url)
                            get_data()
                        } else {
                            console.log('else')
                            next_url = false
                            get_data()
                        }
                    })
                    .catch(error => {
                        reject(error.stack)
                    })
            } else {
                console.log('before resolve')
                resolve(output)
            }
        })
        return await promise
    } catch(e) {
        console.log(e)
    }
}

Это то место, где я скриплю зубами.Мне кажется, что я понимаю эту функцию:

  • она возвращает значение обещания (это то, что я понимаю, return await promise делает)
  • это рекурсивная функция.Итак, если есть next_url, функция продолжается.Но если его нет, его в последний раз вызывают для перехода в часть else, где он разрешает массив output, который содержит результаты (значения, а не состояние) всех обещаний.По крайней мере, когда я выполняю его и проверяю мои проверки работоспособности с помощью console.log, который я написал, он работает.

Итак, output заполнен данными, и это здорово.

Но когда я вызываю эту функцию из другого файла MyOtherFile.js, например:

final_output = []
MyFile.get_data()
    .then(result => {
        console.log('getting data')
        final_output.push(...result)
    })

, она никогда не попадает в часть then.А когда я console.log MyFile.get_data(), это ожидающее обещание.

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

Дайте мне знать, если мой вопрос недостаточно ясен.Я уже два дня чешу голову, и мне кажется, что я бегаю по кругу.

Спасибо за то, что вы классное сообщество!

Ответы [ 3 ]

0 голосов
/ 22 ноября 2018

Это немного не проверено

const api_url = 'https://some_url.com/api/data';

get_data(api_url).then((results) => {
  console.log(results);
}).catch((error) => {
   // console.error(error);
});

function get_items (url) {
  const options = {
    baseURL: url,
    method: 'get'
  };

  return axios(options).then((response) => response.data);
}

async function get_data(next_url) {
  const output = [];

  while (next_url) {
    const { results, next } = await get_items(next_url);
    output.push(...results);
    next_url = next;
  }

  return output;
}

По существу, это немного опрятнее.Я предлагаю взглянуть на другие примеры с Обещаниями и преимуществами, а также с тем, чтобы облегчить ожидание / асинхронность.Следует помнить одну вещь: если вы вернете Обещание, оно будет следовать всей цепочке then и всегда будет возвращать Обещание со значением последнего then .., если это имеет смысл:)

0 голосов
/ 22 ноября 2018

Другой способ сделать это - вообще не использовать async и просто рекурсивно возвращать обещание:

const getItems = (url) =>
  axios({
    baseURL: url,
    method: 'get',
  }).then((response) => response.data);

const getData = (initialUrl) => {
  const recur = (result, nextUrl) =>
    !nextUrl
      ? Promise.resolve(result)
      : getItems(nextUrl).then((data) =>
          recur(result.concat([data.results]), data.next),
        );
  return recur([],initialUrl)
    .catch(e=>Promise.reject(e.stack));//reject with error stack
};

Как отмечает CertainPerformance;вам не нужно перехватывать на каждом уровне, если вы хотите, чтобы getData отклонял с ошибкой. стек вам нужно перехватывать только один раз.

Однако;если у вас было 100 следующих URL-адресов, и 99 из них были в порядке, но только последний не удался, вы хотели бы отклонить таким образом, чтобы результаты сохранялись так долго, чтобы вы могли повторить попытку?

Если вы это сделаете, то кодможет выглядеть примерно так:

const getData = (initialUrl) => {
  const recur = (result, nextUrl) =>
    !nextUrl
      ? Promise.resolve(result)
      : getItems(nextUrl)
        .catch(e=>Promise.reject([e,result]))//reject with error and result so far
        .then((data) =>
          recur(result.concat([data.results]), data.next),
        );
  return recur([],initialUrl);//do not catch here, just let it reject with error and result
};
0 голосов
/ 22 ноября 2018

Есть несколько проблем.Во-первых, вы никогда не resolve начальный Promise, если не введен блок else.Другой способ заключается в том, что вы должны возвращать рекурсивный get_data вызов каждый раз, чтобы он мог быть правильно связан с начальным Promise.Вы также можете отказаться от явного создания обещания. Антипаттерн - get_items уже возвращает Promise, поэтому нет необходимости создавать еще один (то же самое для внутренних вызовов get_items, axios returnPromise s).

Вы можете рассмотреть простой цикл while, переназначая строку next_url до тех пор, пока она не станет ложной:

function get_items (baseURL) {
  const options = {
    baseURL: url,
    method: 'get'
  }
  // return the axios call, handle errors in the consumer instead:
  return axios(options)
    .then(res => res.data)
}

async function get_data() {
  const output = []
  let next_url = 'https://some_url.com/api/data'
  try {
    while (next_url) {
      const response = await get_items(next_url);
      output.push(...response.results)
      next_url = response.next;
    }
  } catch (e) {
    // handle errors *here*, perhaps
    console.log(e)
  }
  return output;
}

Обратите внимание, что .catch приведет кPromise преобразуется из отклоненного Promise в разрешенного один - вы не хотите .catch везде, потому что это затруднит для вызывающего абонентаобнаруживать ошибки.

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