Thời gian chờ yêu cầu API tìm nạp?


100

Tôi có một fetch-api POSTyêu cầu:

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

Tôi muốn biết thời gian chờ mặc định cho việc này là gì? và làm thế nào chúng tôi có thể đặt nó thành một giá trị cụ thể như 3 giây hoặc giây vô định?

Câu trả lời:


78

Chỉnh sửa 1

Như đã chỉ ra trong nhận xét, mã trong câu trả lời ban đầu tiếp tục chạy bộ đếm thời gian ngay cả sau khi lời hứa được giải quyết / từ chối.

Đoạn mã dưới đây khắc phục sự cố đó.

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


Câu trả lời ban đầu

Nó không có một mặc định cụ thể; đặc điểm kỹ thuật không thảo luận về thời gian chờ.

Bạn có thể triển khai trình bao bọc thời gian chờ của riêng mình cho các lời hứa nói chung:

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

Như được mô tả trong https://github.com/github/fetch/issues/175 Nhận xét của https://github.com/mislav


27
Tại sao đây là câu trả lời được chấp nhận? SetTimeout ở đây sẽ tiếp tục hoạt động ngay cả khi lời hứa được giải quyết. Giải pháp tốt hơn sẽ là làm điều này: github.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislav bảo vệ cách tiếp cận của mình ở mức thấp hơn trong chuỗi đó: github.com/github/fetch/issues/175#issuecomment-284787564 . Việc hết thời gian tiếp tục diễn ra không quan trọng, bởi vì yêu cầu .reject()một lời hứa đã được giải quyết sẽ không có tác dụng gì.
Mark Amery

1
mặc dù chức năng 'tìm nạp' bị từ chối bởi thời gian chờ, kết nối tcp nền không bị đóng. Làm cách nào để thoát khỏi quy trình nút của tôi một cách dễ dàng?
Prog Quester

26
DỪNG LẠI! Đây là một câu trả lời không chính xác! Mặc dù, nó trông giống như một giải pháp tốt và hoạt động, nhưng thực tế kết nối sẽ không bị đóng, mà cuối cùng chiếm kết nối TCP (thậm chí có thể là vô hạn - phụ thuộc vào máy chủ). Hãy tưởng tượng giải pháp SAI này được triển khai trong một hệ thống luôn thử lại kết nối - Điều này có thể dẫn đến nghẹt giao diện mạng (quá tải) và cuối cùng làm cho máy của bạn bị treo! @Endless đã đăng câu trả lời chính xác ở đây .
Slavik Meltser

1
@SlavikMeltser Tôi không hiểu. Câu trả lời bạn đã chỉ cũng không làm hỏng kết nối TCP.
Mateus Pires

143

Tôi thực sự thích cách tiếp cận rõ ràng từ ý chính này bằng cách sử dụng 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
Điều này gây ra "Từ chối chưa được xử lý" nếu fetchlỗi xảy ra sau thời gian chờ. Điều này có thể được giải quyết bằng cách xử lý ( .catch) fetchlỗi và bắn lại nếu thời gian chờ chưa xảy ra.
lionello

5
IMHO, điều này có thể được cải thiện hơn với AbortController khi từ chối, hãy xem stackoverflow.com/a/47250621 .
RiZKiT 14/10/19

Sẽ tốt hơn nếu bạn xóa thời gian chờ nếu tìm nạp thành công.
Bob9630

105

Sử dụng AbortController , bạn sẽ có thể thực hiện việc này:

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
Điều này thậm chí còn tốt hơn so với giải pháp hứa hẹn-chủng tộc vì nó có thể hủy bỏ yêu cầu thay vì chỉ thực hiện phản hồi sớm hơn. Sửa cho tôi nếu tôi sai.
Karl Adler

3
Câu trả lời không giải thích AbortController là gì. Ngoài ra, nó là thử nghiệm và cần được lấp đầy trong các công cụ không được hỗ trợ, cũng không phải là một cú pháp.
Estus Flask

Nó có thể không giải thích AbortController là gì (tôi đã thêm một liên kết vào câu trả lời để giúp những người lười biếng dễ dàng hơn), nhưng đây là câu trả lời tốt nhất cho đến nay, vì nó làm nổi bật thực tế rằng chỉ bỏ qua một yêu cầu không có nghĩa là nó vẫn còn không chờ xử lý. Câu trả lời chính xác.
Aurelio

2
"Tôi đã thêm một liên kết vào câu trả lời để làm cho nó dễ dàng hơn cho những người lười biếng" - nó thực sự phải đi kèm với một liên kết và nhiều thông tin hơn theo quy tắc tbh. Nhưng cảm ơn bạn đã cải thiện câu trả lời.
Jay Wick

6
Tốt hơn để có câu trả lời này hơn là không có câu trả lời bởi vì mọi người đặt-off bởi nitpickery, tbh
Michael Terry

21

Dựa trên câu trả lời xuất sắc của Endless ' , tôi đã tạo một chức năng tiện ích hữu ích.

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. Nếu hết thời gian chờ trước khi tài nguyên được tìm nạp thì quá trình tìm nạp sẽ bị hủy bỏ.
  2. Nếu tài nguyên được tìm nạp trước khi hết thời gian chờ thì thời gian chờ sẽ bị xóa.
  3. Nếu tín hiệu đầu vào bị hủy bỏ thì quá trình tìm nạp bị hủy bỏ và thời gian chờ bị xóa.
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
        }
    });

Hy vọng rằng sẽ giúp.


9

chưa có hỗ trợ hết thời gian chờ trong API tìm nạp. Nhưng nó có thể đạt được bằng cách gói nó trong một lời hứa.

ví dụ.

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

tôi thích cái này hơn, ít lặp lại hơn để sử dụng nhiều lần.
dandavis

1
Yêu cầu không bị hủy sau thời gian chờ ở đây, đúng không? Điều này có thể ổn đối với OP, nhưng đôi khi bạn muốn hủy yêu cầu phía máy khách.
chạy thận

2
@trysis tốt, vâng. Gần đây đã triển khai một giải pháp để hủy tìm nạp bằng AbortController , nhưng vẫn đang thử nghiệm với sự hỗ trợ hạn chế của trình duyệt. Thảo luận
code-jaff

Thật buồn cười, IE & Edge là những người duy nhất hỗ trợ nó! Trừ khi trang web Mozilla dành cho thiết bị di động hoạt động trở lại ...
phân tích

Firefox đã hỗ trợ nó từ năm 57. :: xem tại Chrome ::
Franklin Yu

7

CHỈNH SỬA : Yêu cầu tìm nạp sẽ vẫn chạy trong nền và rất có thể sẽ ghi lại lỗi trong bảng điều khiển của bạn.

Quả thực là Promise.racecách tiếp cận tốt hơn.

Xem liên kết này để tham khảo Promise.race ()

Cuộc đua có nghĩa là tất cả các Lời hứa sẽ chạy cùng một lúc và cuộc đua sẽ dừng lại ngay khi một trong các Lời hứa trả về giá trị. Do đó, chỉ một giá trị sẽ được trả về . Bạn cũng có thể chuyển một hàm để gọi nếu quá trình tìm nạp hết thời gian.

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

Nếu điều này thu hút sự quan tâm của bạn, một triển khai khả thi sẽ là:

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

Bạn có thể tạo một trình bao bọc thời gian chờ

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

Sau đó, bạn có thể kết thúc bất kỳ lời hứa nào

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

Nó sẽ không thực sự hủy kết nối cơ bản nhưng sẽ cho phép bạn hết thời gian thực hiện một lời hứa.
Tài liệu tham khảo


2

Nếu bạn chưa định cấu hình thời gian chờ trong mã của mình, Nó sẽ là thời gian chờ yêu cầu mặc định của trình duyệt của bạn.

1) Firefox - 90 giây

about:configtrong lĩnh vực Firefox URL. Tìm giá trị tương ứng với khóanetwork.http.connection-timeout

2) Chrome - 300 giây

Nguồn


0
  fetchTimeout (url,options,timeout=3000) {
    return new Promise( (resolve, reject) => {
      fetch(url, options)
      .then(resolve,reject)
      setTimeout(reject,timeout);
    })
  }

Điều này khá giống với stackoverflow.com/a/46946588/1008999 nhưng bạn có thời gian chờ mặc định
Endless

0

Sử dụng c-promise2 lib, tìm nạp có thể hủy bỏ với thời gian chờ có thể trông giống như sau (Bản trình diễn jsfiddle trực tiếp ):

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

Mã này dưới dạng tìm nạp cp npm

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.