может ли асинхронизация с выборкой опроса до тех пор, пока не будет выполнено условие? (пережить отклонение)

#javascript #async-await #fetch #es6-promise

#javascript #асинхронный-ожидание #выборка #es6-обещание

Вопрос:

Используя fetch API и async / await, можно ли продолжать опрос бесконечно, независимо от доступности URL? Я ожидаю, что URL-адрес может стать доступным в конечном итоге, поэтому я хочу продолжать попытки, пока не будет выполнено условие. Попытался придумать минимальный жизнеспособный пример кода, и я не уверен, что мне это удалось:

 // this is just a placeholder. It will eventually be a function 
// that evaluates something real. 
// Assume validContinue gets updated elsewhere.      
function shouldContinue() {
    return validContinue;
}
      
async function wonderPoll(someUrl) {

  // just a delay mechanism
  function wait(ms = 1000) {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  }

  // the actual individual poll
  async function pollingFunction(url) {

    const response = await fetch(url, {
      cache: 'no-store'
    });

    if (response.ok) {
      return response;
    } else {
      Promise.reject(response);
    }

  }

  // allegedly keep polling until condition is met. 
  // But the rejected Promise is breaking out!
  while (shouldContinue()) {
    await wait();
    result = await pollingFunction(someUrl);
  }
  
  // when the fetch hits a rejected state, we never get here!
  console.log('done with the while loop, returning last successful result')

  return resu<
}

const sampleUrl = 'https://get.geojs.io/v1/ip/country.json?ip=8.8.8.8';
const sampleUrl2 = 'http://totallybroken_fo_sho';

// swap the URL to test
wonderPoll(sampleUrl)
  .then((result) => {
    console.log('got a result', result)
  })
  .catch((err) => {
    console.log('got an error', err)
  });
  

Я вижу, что происходит (я думаю). Родительский вызов в конечном итоге выполняет функцию опроса, которая отклоняет обещание. Условие продолжения все еще теоретически выполняется, но отклонение выходит из цикла While и отправляется на отклонение непосредственно вверх. Это распространяется вплоть до метода catch исходного / начального обещания. Он даже не попадает ни в какой код, который появился бы после цикла While в случае разрешенных обещаний.

Чего я не знаю, так это как предотвратить это. Я думаю, что я не понимаю синтаксис для перехвата и разрешения обещания. Когда я заменяю Promise.reject в анализаторе ответов на Promise.resolve(response) , он все равно отклоняется до самого верха.

Если предоставленный мной URL-адрес действителен, он будет продолжаться до тех пор, пока условие больше не будет выполнено.


Вот скрипка: https://jsfiddle.net/gregpettit/qf495bjm/5 /

Чтобы использовать скрипку, кнопка «стоп» имитирует выполнение условия, и я предоставил два разных URL-адреса, которые нужно поменять местами вручную (передав someUrl или someUrl2) для тестирования.

Ожидаемые результаты:

  • при хорошем URL-адресе непрерывный опрос (придется копаться в сети в инструментах разработчика) до тех пор, пока не будет выполнено условие (нажав Стоп!), А затем вызывающая функция ‘then’ может показать результат.
  • при неправильном URL-адресе непрерывный опрос до тех пор, пока не будет выполнено условие, а затем вызов функции ‘catch’ показывает ошибку

Фактические результаты:

  • положительный тестовый пример в порядке
  • отрицательный тестовый пример переходит непосредственно к перехвату

Комментарии:

1. Я рассматривал возможность просто завершить стандартный XMLHttpRequest обещанием и явно разрешить или намеренно НЕ отклонять (по ошибке)… может быть, выборка слишком лаконична и негибка для моих нужд?

2. Я бы всегда разрешал с каким-либо указанием успеха / неудачи в разрешенном значении… затем, когда «условие выполнено», верните в случае успеха, отклоните (выбросьте) в случае сбоя

3. что Promise.reject(response); делать, поскольку вы отбрасываете возвращаемое значение?

4. Рон, я новичок в этом, так что, возможно, ты поймал меня на оплошности. Я бы никогда не пропустил это мимо ушей. Но я понимаю так: fetch API фактически обрабатывает закодированные «ошибки» как разрешения, а не отклонения. Но свойство ответа «ok» равно false . Итак, я пытаюсь учесть и это. Я мог бы сделать это неправильно. 😉

Ответ №1:

Вы можете try…catch это сделать, чтобы предотвратить выход из цикла.

 while (shouldContinue()) {
  try {
    await wait();
    result = await pollingFunction(someUrl);
  } catch (e) {}
}
  

Комментарии:

1. Попробовал. Настолько просто, насколько это возможно! Согласно образцу Jaromanda X, я думаю, что отправка причины отклонения является приятной, но это определенно соответствует требованиям «игнорировать отклонение и продолжить» и было легко понять.

Ответ №2:

Измените код в цикле while на try / catch, чтобы вы могли перехватить ошибку

результат может содержать значение, когда ошибки нет, или причину, когда есть ошибка

Как только цикл остановлен, вы либо возвращаете значение, либо выбрасываете с указанием причины

Как показано ниже

 async function wonderPoll(someUrl) {

  // just a delay mechanism
  function wait(ms = 1000) {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  }

  // the actual individual poll
  async function pollingFunction(url) {

    const response = await fetch(url, {
      cache: 'no-store'
    });

    if (response.ok) {
      return response;
    } else {
      Promise.reject(response);
    }

  }

  // allegedly keep polling until condition is met. But the rejected Promise is breaking out!
  while (shouldContinue()) {
    try {
      await wait();
      const value = await pollingFunction(someUrl);
      result = {value};
    } catch (reason) {
      result = {reason};
    }
  }
  // when the fetch hits a rejected state, we never get here!
  console.log('done with the while loop, returning last successful result')
  if (result.reason) {
    throw result.reason;
  }
  return result.value;
}
  

Запущенный пример https://jsfiddle.net/twkbo9pg /

пример включает status в себя результат, но в этом нет необходимости (я позаимствовал код из своего Promise.allSettled polyfill и забыл удалить это свойство)

Комментарии:

1. Имеет смысл. Мне также понравились ваши ярлыки для приведения / переноса значений и причин… не буду врать, я никогда раньше так не делал.

2. это просто сокращение ES6 для result = {value:value}

3. Я только недавно обновил свой набор навыков… Я провел много времени в ES5-land, и у меня не было ежедневной пропускной способности, чтобы идти в ногу со временем. Но эти дни настали. Потребовался всего день, чтобы преодолеть мою первоначальную (и иррациональную) неприязнь к функциям со стрелками. 😉 Кстати, это работало в производственном коде. Который на самом деле вложен в несколько других обещаний. Сегодня вечером я смогу лечь спать в разумное время! Приветствия.

Ответ №3:

возможно, вы захотите проверить наблюдаемые потоки! Если вы собираетесь получать много данных с течением времени, это все rxjs.

На самом деле есть несколько способов сделать это, если это кажется странным (это вроде как хаха).

 import { ajax } from "rxjs/ajax";
import { duration } from "moment-timezone"; // I copied this from some old code... whatever.
import { catchError, map, share, switchMap } from "rxjs/operators";

const baseUrl = "http://foo.bar"
const base = (method, headers = {}) => ({
  method,
  headers: {
    Accept: "application/json",
    ...headers,
  },
  crossDomain: true,
  withCredentials: true,
})

const ajaxGet = url => ajax({ ...base("GET"), url })

export const userEpic = timer(0, duration(5, "minutes").asMilliseconds()).pipe(
  switchMap(() =>
    ajaxGet(`${baseUrl}/users`).pipe(
      map(({ response }) => getUsersSuccess(response)),
      catchError(e => of(getUsersError(e))),
    )
  ),
  share()
)
  

Комментарии:

1. Спасибо, сэр. Я положу это в задний карман и изучу rxjs для своих будущих нужд! Грег в настоящем времени должен сделать это в идеале сегодня вечером. 😉

Ответ №4:

Две вещи

  } else {
            Promise.reject(response);
        }
  

должен вернуть это. Сейчас это работает «случайно».

  } else {
            return Promise.reject(response);
        }
  

Во-вторых, result = await pollingFunction(someUrl); возможно, захочется добавить к нему .catch:
result = await pollingFunction(someUrl).catch(_=>null); или что-то, что можно протестировать во вложении while

Но я думаю, что вы можете упростить все это таким образом:

 export async function wonderPoll(someUrl) {
    while (shouldContinue()) {
        await wait();

        const response = await fetch(someUrl, { cache: 'no-store' });

        if (response.ok)
            return response;
    }
    return Promise.reject(); // only if !shouldContinue()
}

  

Комментарии:

1. Увы, я не смог заставить это работать. Здесь была попытка сделать для этого скрипку: jsfiddle.net/gregpettit/cwskm3hf/3