API isteği zaman aşımı getirilsin mi?


100

Bir fetch-api POSTisteğim var:

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

Bunun için varsayılan zaman aşımının ne olduğunu bilmek istiyorum. ve bunu 3 saniye veya belirsiz saniye gibi belirli bir değere nasıl ayarlayabiliriz?

Yanıtlar:


78

Düzenle 1

Yorumlarda belirtildiği gibi, orijinal cevaptaki kod, söz çözüldükten / reddedildikten sonra bile zamanlayıcıyı çalıştırmaya devam eder.

Aşağıdaki kod bu sorunu düzeltir.

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)
      })
  })
}


Orijinal cevap

Belirli bir varsayılanı yoktur; şartname zaman aşımlarını hiç tartışmaz.

Genel olarak vaatler için kendi zaman aşımı sarmalayıcınızı uygulayabilirsiniz:

// 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
})

Açıklandığı gibi https://github.com/github/fetch/issues/175 isimli kullanıcının yorumu https://github.com/mislav


27
Bu neden kabul edilen cevap? Buradaki setTimeout, sözler çözülse bile devam edecektir. Bunu yapmak daha iyi bir çözüm olabilir: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav, yaklaşımını bu başlıkta daha aşağıda savunuyor: github.com/github/fetch/issues/175#issuecomment-284787564 . Zaman aşımının devam edip etmediği önemli değil, çünkü .reject()zaten çözülmüş olan bir sözü çağırmak hiçbir şey yapmaz.
Mark Amery

1
'getirme' işlevi zaman aşımı tarafından reddedilmesine rağmen, arka plan tcp bağlantısı kapatılmaz. Düğüm sürecimden nasıl zarif bir şekilde çıkabilirim?
Prog Quester

26
DUR! Bu yanlış bir cevap! Her ne kadar iyi ve çalışan bir çözüm gibi görünse de, aslında bağlantı kapanmayacaktır, bu da sonunda bir TCP bağlantısını işgal eder (sonsuz bile olabilir - sunucuya bağlıdır). Bu YANLIŞ çözümün, her zaman bir bağlantıyı yeniden deneyen bir sistemde uygulanacağını hayal edin - Bu, ağ arayüzünün boğulmasına (aşırı yüklenmesine) neden olabilir ve makinenizin sonunda askıda kalmasına neden olabilir! @Endless burada doğru cevabı yayınladı .
Slavik Meltser

1
@SlavikMeltser Anlamıyorum. Gösterdiğiniz yanıt TCP bağlantısını da kesmiyor.
Mateus Pires

143

Gerçekten bu temiz yaklaşım gibi özünden kullanarak 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
Zaman aşımından sonra bir fetchhata meydana gelirse , bu bir "İşlenmemiş ret" durumuna neden olur . Bu , hatayı ele alarak ( ) ve zaman aşımı henüz gerçekleşmediyse yeniden atarak çözülebilir . .catchfetch
lionello

5
IMHO bu, reddederken AbortController ile daha da geliştirilebilir, bkz. Stackoverflow.com/a/47250621 .
RiZKiT

Getirme de başarılı olursa zaman aşımını temizlemek daha iyi olur.
Bob9630

105

AbortController'ı kullanarak bunu yapabilirsiniz:

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
Bu, sözlü yarış çözümünden bile daha iyi görünüyor çünkü muhtemelen sadece önceki yanıtı almak yerine isteği iptal ediyor. Yanlışsam düzelt.
Karl Adler

3
Cevap, AbortController'ın ne olduğunu açıklamıyor. Ayrıca, deneyseldir ve desteklenmeyen motorlarda çoklu doldurulması gerekir, ayrıca bir sözdizimi değildir.
Estus Flask

AbortController'ın ne olduğunu açıklamayabilir (tembeller için işi kolaylaştırmak için cevaba bir bağlantı ekledim), ancak şu ana kadarki en iyi cevap bu, çünkü sadece bir isteği görmezden gelmenin hala olduğu anlamına gelmediğini vurguluyor. beklemede değil. Mükemmel cevap.
Aurelio

2
"Tembellerin işini kolaylaştırmak için cevaba bir bağlantı ekledim" - gerçekten bir bağlantı ve kurallara göre daha fazla bilgi ile birlikte gelmelidir tbh. Ancak cevabı geliştirdiğiniz için teşekkür ederim.
Jay Wick

6
Bu yanıtı almak, yanıt vermekten daha iyidir, çünkü insanlar nitpickery tarafından erteleniyor, tbh
Michael Terry

21

Endless'ın mükemmel cevabını temel alarak yararlı bir yardımcı program işlevi oluşturdum.

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. Kaynak getirilmeden önce zaman aşımına ulaşılırsa, getirme iptal edilir.
  2. Kaynak, zaman aşımına ulaşılmadan önce getirilirse, zaman aşımı temizlenir.
  3. Giriş sinyali iptal edilirse, getirme iptal edilir ve zaman aşımı silinir.
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
        }
    });

Umarım yardımcı olur.


9

Getirme API'sinde henüz zaman aşımı desteği yok. Ama bir sözün içine sarılarak elde edilebilir.

örneğin.

  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);
      }
    });
  }

Bunu daha çok seviyorum, birden fazla kez kullanmak daha az tekrar ediyor.
dandavis

1
Buradaki zaman aşımından sonra istek iptal edilmiyor, değil mi? Bu OP için iyi olabilir, ancak bazen istemci tarafında bir talebi iptal etmek istersiniz.
trysis

2
@trysis iyi, evet. Yakın zamanda AbortController ile getirmeyi iptal etmek için bir çözüm uyguladı , ancak sınırlı tarayıcı desteği ile hala deneysel. Tartışma
code-jaff

Bu komik, IE ve Edge onu destekleyenler sadece! Mobil Mozilla sitesi tekrar harekete
geçmedikçe

Firefox 57'den beri destekliyor. :: Chrome'da izliyor ::
Franklin Yu

7

DÜZENLEME : Getirme isteği arka planda çalışmaya devam edecek ve büyük olasılıkla konsolunuzda bir hata kaydedecektir.

Aslında Promise.raceyaklaşım daha iyidir.

Promise.race () referansı için bu bağlantıya bakın

Yarış, tüm Vaatlerin aynı anda çalışacağı ve vaatlerden biri bir değer verir vermez yarışın duracağı anlamına gelir. Bu nedenle, yalnızca bir değer döndürülür . Getirme zaman aşımına uğrarsa çağırmak için bir işlev de iletebilirsiniz.

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

Bu ilginizi çekerse, olası bir uygulama şöyle olacaktır:

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

Bir zaman aşımı oluşturabilirsin

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

Daha sonra herhangi bir sözü yerine getirebilirsiniz

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

Aslında temeldeki bir bağlantıyı iptal etmez, ancak bir sözün zaman aşımına uğramasına izin verir.
Referans


2

Kodunuzda zaman aşımı yapılandırmadıysanız, bu, tarayıcınızın varsayılan istek zaman aşımı olacaktır.

1) Firefox - 90 saniye

Tip about:configFirefox URL alanına. Anahtara karşılık gelen değeri bulunnetwork.http.connection-timeout

2) Chrome - 300 saniye

Kaynak



0

Kullanımı c-promise2 bu bir (gibi görünebilir zaman aşımıyla getirme iptal lib Canlı jsfiddle demo ):

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

Bu kod bir npm paketi olarak cp-getirme

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.