#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