Час очікування запиту на отримання API?


100

У мене є fetch-api POSTзапит:

fetch(url, {
  method: 'POST',
  body: formData,
  credentials: 'include'
})

Я хочу знати, який час очікування за замовчуванням для цього? і як ми можемо встановити для нього певне значення, наприклад 3 секунди або невизначені секунди?

Відповіді:


78

Редагувати 1

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

Код нижче виправляє цю проблему.

function timeout(ms, promise) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(() => {
      reject(new Error('TIMEOUT'))
    }, ms)

    promise
      .then(value => {
        clearTimeout(timer)
        resolve(value)
      })
      .catch(reason => {
        clearTimeout(timer)
        reject(reason)
      })
  })
}


Оригінальна відповідь

Він не має вказаного за замовчуванням; специфікація взагалі не обговорює тайм-аути.

Ви можете застосувати власну обгортку тайм-ауту для обіцянок загалом:

// Rough implementation. Untested.
function timeout(ms, promise) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      reject(new Error("timeout"))
    }, ms)
    promise.then(resolve, reject)
  })
}

timeout(1000, fetch('/hello')).then(function(response) {
  // process response
}).catch(function(error) {
  // might be a timeout error
})

Як описано в https://github.com/github/fetch/issues/175 Коментар https://github.com/mislav


27
Чому це прийнята відповідь? Тут setTimeout буде продовжувати працювати, навіть якщо обіцянка вирішується. Кращим рішенням було б зробити це: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav захищає свій підхід нижче в цій темі: github.com/github/fetch/issues/175#issuecomment-284787564 . Немає значення, що тайм-аут триває, адже заклик .reject()до вже вирішеної обіцянки нічого не робить.
Mark Amery

1
хоча функція 'fetch' відхиляється через час очікування, фонове з'єднання tcp не закрито. Як я можу вийти з мого процесу вузла витончено?
Prog Quester

26
СТОП! Це неправильна відповідь! Хоча, це виглядає як хороше і робоче рішення, але насправді підключення не буде закрито, що з часом займає TCP-з'єднання (може бути навіть нескінченним - залежить від сервера). Уявіть, що це НЕПРАВИЛЬНЕ рішення буде впроваджено в систему, яка повторює підключення кожного періоду часу - це може призвести до задушення мережевого інтерфейсу (перевантаження) і з часом ваша машина зависне! @Endless опублікував правильну відповідь тут .
Славік Мельцер

1
@SlavikMeltser Я не розумію. Відповідь, яку ви вказали, також не порушує з'єднання TCP.
Матеус Пірес

143

Мені дуже подобається чіткий підхід цього суті за допомогою Promise.race

fetchWithTimeout.js

export default function (url, options, timeout = 7000) {
    return Promise.race([
        fetch(url, options),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('timeout')), timeout)
        )
    ]);
}

main.js

import fetch from './fetchWithTimeout'

// call as usual or with timeout as 3rd argument

fetch('http://google.com', options, 5000) // throw after max 5 seconds timeout error
.then((result) => {
    // handle result
})
.catch((e) => {
    // handle errors and timeout error
})

2
Це спричиняє "необроблене відхилення", якщо fetchпомилка трапляється після таймауту. Цю проблему можна вирішити, усунувши ( .catch) fetchпомилку та повернувши, якщо тайм-аут ще не відбувся.
lionello

5
ІМХО це може бути поліпшена futher з AbortController при відхиленні см stackoverflow.com/a/47250621 .
RiZKiT

Було б краще очистити час очікування, якщо вибір також успішний.
Bob9630

105

Використовуючи AbortController , ви зможете зробити це:

const controller = new AbortController();
const signal = controller.signal;

const fetchPromise = fetch(url, {signal});

// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000);


fetchPromise.then(response => {
  // completed request before timeout fired

  // If you only wanted to timeout the request, not the response, add:
  // clearTimeout(timeoutId);
})

14
Це виглядає навіть краще, ніж рішення обіцянки-гонки, оскільки воно, ймовірно, скасовує запит, а не просто бере попередню відповідь. Виправте мене, якщо я помиляюся.
Карл Адлер,

3
Відповідь не пояснює, що таке AbortController. Крім того, він експериментальний і його потрібно заповнювати в непідтримуваних двигунах, також це не синтаксис.
Estus Flask,

Це може не пояснити, що таке AbortController (я додав посилання на відповідь, щоб полегшити для ледачих), але це найкраща відповідь на даний момент, оскільки підкреслює той факт, що просто ігнорування запиту не означає, що він все ще не очікує на розгляд. Чудова відповідь.
Aurelio

2
"Я додав посилання до відповіді, щоб полегшити ледачих" - воно дійсно має містити посилання та більше інформації відповідно до правил tbh. Але дякую за покращення відповіді.
Jay Wick,

6
Краще отримати цю відповідь, ніж не відповісти, тому що людей відкладає
Майкл Террі

21

Спираючись на чудову відповідь Endless , я створив корисну функцію утиліти.

const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
    const controller = new AbortController();
    const promise = fetch(url, { signal: controller.signal, ...options });
    if (signal) signal.addEventListener("abort", () => controller.abort());
    const timeout = setTimeout(() => controller.abort(), ms);
    return promise.finally(() => clearTimeout(timeout));
};
  1. Якщо тайм-аут досягнуто до отримання ресурсу, тоді отримання скасовано.
  2. Якщо ресурс отримується до досягнення часу очікування, час очікування очищається.
  3. Якщо вхідний сигнал перервано, тоді вилучення перервано, і час очікування очищений.
const controller = new AbortController();

document.querySelector("button.cancel").addEventListener("click", () => controller.abort());

fetchTimeout("example.json", 5000, { signal: controller.signal })
    .then(response => response.json())
    .then(console.log)
    .catch(error => {
        if (error.name === "AbortError") {
            // fetch aborted either due to timeout or due to user clicking the cancel button
        } else {
            // network error or json parsing error
        }
    });

Сподіваюся, що це допомагає.


9

в API вибору поки немає підтримки часу очікування. Але цього можна досягти, обгорнувши це обіцянкою.

наприклад,

  function fetchWrapper(url, options, timeout) {
    return new Promise((resolve, reject) => {
      fetch(url, options).then(resolve, reject);

      if (timeout) {
        const e = new Error("Connection timed out");
        setTimeout(reject, timeout, e);
      }
    });
  }

мені подобається цей, кращий, менш повторюваний для використання більше одного разу.
dandavis

1
Запит не скасовується після таймауту тут, правильно? Це може бути добре для OP, але іноді ви хочете скасувати запит на стороні клієнта.
триз

2
@trysis ну, так. Нещодавно було впроваджено рішення для скасування отримання з AbortController , але все ще експериментальне з обмеженою підтримкою браузера. Обговорення
code-jaff

Це смішно, IE & Edge - єдині, хто підтримує це! Якщо мобільний сайт Mozilla знову не
запрацює

Firefox підтримує його з 57. :: перегляд у Chrome ::
Франклін Ю

7

РЕДАГУВАТИ : Запит на отримання все ще буде виконуватися у фоновому режимі і, швидше за все, буде відображати помилку у вашій консолі.

Дійсно, Promise.raceпідхід кращий.

Див. Це посилання для довідки Promise.race ()

Перегон означає, що всі обіцянки будуть виконуватися одночасно, і перегони припиняться, як тільки одна з обіцянок поверне значення. Тому буде повернуто лише одне значення . Ви також можете передати функцію для виклику, якщо час вибору закінчується.

fetchWithTimeout(url, {
  method: 'POST',
  body: formData,
  credentials: 'include',
}, 5000, () => { /* do stuff here */ });

Якщо це викликає у вас інтерес, можливою реалізацією буде:

function fetchWithTimeout(url, options, delay, onTimeout) {
  const timer = new Promise((resolve) => {
    setTimeout(resolve, delay, {
      timeout: true,
    });
  });
  return Promise.race([
    fetch(url, options),
    timer
  ]).then(response => {
    if (response.timeout) {
      onTimeout();
    }
    return response;
  });
}

2

Ви можете створити обгортку timeoutPromise

function timeoutPromise(timeout, err, promise) {
  return new Promise(function(resolve,reject) {
    promise.then(resolve,reject);
    setTimeout(reject.bind(null,err), timeout);
  });
}

Потім можна обернути будь-яку обіцянку

timeoutPromise(100, new Error('Timed Out!'), fetch(...))
  .then(...)
  .catch(...)  

Це насправді не скасує базового з'єднання, але дозволить вам очікувати обіцянки.
Довідково


2

Якщо ви не налаштували час очікування у своєму коді, це буде час очікування запиту за замовчуванням для вашого браузера.

1) Firefox - 90 секунд

Введіть about:configполе URL-адреси Firefox. Знайдіть значення, яке відповідає ключуnetwork.http.connection-timeout

2) Хром - 300 секунд

Джерело



0

За допомогою c-promis2 lib скасовуване завантаження з таймаутом може виглядати так ( демонстрація Live jsfiddle ):

import CPromise from "c-promise2"; // npm package

function fetchWithTimeout(url, {timeout, ...fetchOptions}= {}) {
    return new CPromise((resolve, reject, {signal}) => {
        fetch(url, {...fetchOptions, signal}).then(resolve, reject)
    }, timeout)
}
        
const chain = fetchWithTimeout("https://run.mocky.io/v3/753aa609-65ae-4109-8f83-9cfe365290f0?mocky-delay=10s", {timeout: 5000})
    .then(request=> console.log('done'));
    
// chain.cancel(); - to abort the request before the timeout

Цей код як пакет npm cp-fetch

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.