Ambil waktu tunggu permintaan API?


100

Saya punya fetch-api POSTpermintaan:

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

Saya ingin tahu apa waktu tunggu default untuk ini? dan bagaimana kita bisa mengaturnya ke nilai tertentu seperti 3 detik atau detik tak terbatas?

Jawaban:


78

Edit 1

Seperti yang ditunjukkan dalam komentar, kode dalam jawaban asli tetap menjalankan pengatur waktu bahkan setelah janji diselesaikan / ditolak.

Kode di bawah memperbaiki masalah itu.

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


Jawaban asli

Ini tidak memiliki default yang ditentukan; spesifikasi tidak membahas batas waktu sama sekali.

Anda dapat mengimplementasikan pembungkus waktu tunggu Anda sendiri untuk promise secara umum:

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

Seperti dijelaskan di https://github.com/github/fetch/issues/175 Komentar oleh https://github.com/mislav


27
Mengapa ini jawaban yang diterima? SetTimeout di sini akan terus berjalan meskipun janjinya telah terpecahkan. Solusi yang lebih baik adalah melakukan ini: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav membela pendekatannya lebih rendah di utas itu: github.com/github/fetch/issues/175#issuecomment-284787564 . Tidak masalah jika batas waktu terus berjalan, karena .reject()memenuhi janji yang sudah diselesaikan tidak akan menghasilkan apa-apa.
Mark Amery

1
meskipun fungsi 'ambil' ditolak oleh waktu tunggu, koneksi tcp latar belakang tidak ditutup. Bagaimana cara menghentikan proses node saya dengan baik?
Prog Quester

26
BERHENTI! Ini jawaban yang salah! Meskipun, ini terlihat seperti solusi yang bagus dan berfungsi, tetapi sebenarnya koneksi tidak akan ditutup, yang pada akhirnya menempati koneksi TCP (bahkan bisa tak terbatas - tergantung pada server). Bayangkan solusi SALAH ini untuk diterapkan dalam sistem yang mencoba kembali koneksi setiap periode waktu - Ini dapat menyebabkan antarmuka jaringan mati lemas (kelebihan beban) dan membuat mesin Anda hang pada akhirnya! @Endless memposting jawaban yang benar di sini .
Slavik Meltser

1
@Slavikeltser Saya tidak mengerti. Jawaban yang Anda tunjuk juga tidak memutuskan koneksi TCP.
Mateus Pires

143

Saya sangat suka pendekatan bersih dari inti ini menggunakan 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
Ini menyebabkan "penolakan tidak tertangani" jika fetchkesalahan terjadi setelah waktu tunggu. Ini bisa diselesaikan dengan menangani ( .catch) fetchkegagalan dan melempar ulang jika waktu tunggu belum terjadi.
lionello

5
IMHO ini dapat ditingkatkan lebih lanjut dengan AbortController saat menolak, lihat stackoverflow.com/a/47250621 .
RiZKiT

Akan lebih baik untuk menghapus waktu tunggu jika pengambilan berhasil juga.
Bob9630

105

Dengan menggunakan AbortController , Anda dapat melakukan ini:

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
Ini terlihat lebih baik daripada solusi-perlombaan-janji karena itu mungkin membatalkan permintaan daripada hanya menerima tanggapan sebelumnya. Koreksi saya jika saya salah.
Karl Adler

3
Jawabannya tidak menjelaskan apa itu AbortController. Selain itu, ini bersifat eksperimental dan perlu di-polyfill di mesin yang tidak didukung, juga bukan sintaks.
Estus Flask

Ini mungkin tidak menjelaskan apa itu AbortController (saya menambahkan tautan ke jawaban untuk memudahkan mereka yang malas), tetapi ini adalah jawaban terbaik sejauh ini, karena menyoroti fakta bahwa hanya mengabaikan permintaan tidak berarti masih tidak menunggu keputusan. Jawaban yang bagus.
Aurelio

2
"Saya menambahkan tautan ke jawaban untuk memudahkan yang malas" - itu harus benar-benar datang dengan tautan dan informasi lebih lanjut sesuai aturan tbh. Tapi terima kasih telah meningkatkan jawabannya.
Jay Wick

6
Lebih baik memiliki jawaban ini daripada tidak ada jawaban karena orang-orang menunda oleh nitpickery, tbh
Michael Terry

21

Berdasarkan jawaban yang sangat baik dari Endless , saya membuat fungsi utilitas yang bermanfaat.

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. Jika waktu tunggu tercapai sebelum sumber daya diambil, pengambilan dibatalkan.
  2. Jika sumber daya diambil sebelum waktu tunggu tercapai, maka waktu tunggu dihapus.
  3. Jika sinyal input dibatalkan maka pengambilan dibatalkan dan batas waktu dihapus.
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
        }
    });

Semoga membantu.


9

belum ada dukungan waktu tunggu dalam fetch API. Tapi itu bisa dicapai dengan membungkusnya dengan janji.

untuk mis.

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

Saya lebih suka yang ini, tidak terlalu berulang untuk digunakan lebih dari sekali.
dandavis

1
Permintaan tidak dibatalkan setelah batas waktu di sini, benar? Ini mungkin bagus untuk OP, tetapi terkadang Anda ingin membatalkan permintaan sisi klien.
trisis

2
@trysis baik, ya. Baru-baru ini menerapkan solusi untuk abort fetch dengan AbortController , tetapi masih eksperimental dengan dukungan browser terbatas. Diskusi
code-jaff

Lucu sekali, hanya IE & Edge yang mendukungnya! Kecuali jika situs Mozilla seluler beraksi lagi ...
trysis

Firefox telah mendukungnya sejak 57. :: menonton di Chrome ::
Franklin Yu

7

EDIT : Permintaan pengambilan akan tetap berjalan di latar belakang dan kemungkinan besar akan mencatat kesalahan di konsol Anda.

Memang Promise.racependekatannya lebih baik.

Lihat tautan ini untuk referensi Promise.race ()

Balapan berarti semua Janji akan berjalan pada waktu yang sama, dan balapan akan berhenti segera setelah salah satu Janji mengembalikan nilai. Oleh karena itu, hanya satu nilai yang akan dikembalikan . Anda juga bisa meneruskan fungsi untuk dipanggil jika waktu pengambilan habis.

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

Jika ini menarik minat Anda, penerapan yang mungkin adalah:

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

Anda dapat membuat pembungkus timeoutPromise

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

Anda kemudian dapat membungkus janji apa pun

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

Ini tidak akan benar-benar membatalkan koneksi yang mendasarinya tetapi akan memungkinkan Anda untuk membuat janji habis waktu.
Referensi


2

Jika Anda belum mengonfigurasi waktu tunggu dalam kode Anda, Ini akan menjadi waktu tunggu permintaan default di browser Anda.

1) Firefox - 90 detik

Ketik about:configdi bidang URL Firefox. Temukan nilai yang sesuai dengan kuncinetwork.http.connection-timeout

2) Chrome - 300 detik

Sumber



0

Menggunakan c-promise2 lib, pengambilan yang dapat dibatalkan dengan waktu tunggu mungkin terlihat seperti ini ( Demo jsfiddle langsung ):

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

Kode ini sebagai paket npm cp-fetch

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.