APIリクエストのタイムアウトを取得しますか?


105

私が持っている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)
      })
  })
}


元の回答

デフォルトは指定されていません。仕様でタイムアウトについてはまったく説明されていません。

一般に、promiseに対して独自のタイムアウトラッパーを実装できます。

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


28
なぜこれが受け入れられた答えなのですか?ここでのsetTimeoutは、promiseが解決されても続行されます。:より良い解決策は、これを行うことであろうgithub.com/github/fetch/issues/175#issuecomment-216791333
radtad

3
@radtad mislavは、そのスレッドの下位で彼のアプローチを擁護しています:github.com/github/fetch/issues/175#issuecomment-284787564.reject()すでに解決されているpromiseを呼び出しても何も起こらないため、タイムアウトが続くことは問題ではありません。
マークアメリー

1
'fetch'関数はタイムアウトによって拒否されますが、バックグラウンドtcp接続は閉じられません。ノードプロセスを正常に終了するにはどうすればよいですか?
ProgQuester19年

28
やめる!これは間違った答えです!それは良い解決策のように見えますが、実際には接続は閉じられず、最終的にTCP接続を占有します(無限になる可能性もあります-サーバーによって異なります)。この間違ったソリューションが、一定期間ごとに接続を再試行するシステムに実装されることを想像してみてください。これにより、ネットワークインターフェイスが窒息し(過負荷)、最終的にマシンがハングする可能性があります。@Endlessはここに正解を投稿しました
SlavikMeltser19年

2
@SlavikMeltserわかりません。あなたが指摘した答えは、TCP接続も切断しません。
MateusPires20年

147

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エラーが発生し場合、「未処理の拒否」が発生ます。これは.catchfetch失敗を処理し()、タイムアウトがまだ発生していない場合は再スローすることで解決できます。
lionello

7
拒否したときに私見では、これは参照、AbortControllerとのfutherを改善することができstackoverflow.com/a/47250621を
RiZKiT

フェッチも成功した場合は、タイムアウトをクリアすることをお勧めします。
Bob96 3020

114

プロミスレースソリューションを使用すると、リクエストがハングしたままになり、バックグラウンドで帯域幅が消費され、処理中の同時リクエストの最大許容数が低下します。

代わりに、AbortControllerを使用して、実際にリクエストを中止します。例を次に示します。

const controller = new AbortController()

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

fetch(url, { signal: controller.signal }).then(response => {
  // completed request before timeout fired

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

AbortControllerは、フェッチだけでなく、読み取り/書き込み可能なストリームにも使用できます。より新しい関数(特にpromiseベースの関数)はこれをますます使用します。NodeJSは、そのストリーム/ファイルシステムにもAbortControllerを実装しています。私はウェブBluetoothもそれを調べていることを知っています


16
これは、promise-race-solutionよりもさらに見栄えがよくなります。これは、以前の応答を受け取るだけでなく、おそらく要求を中止するためです。私が間違っている場合は私を訂正してください。
Karl Adler

3
答えはAbortControllerが何であるかを説明していません。また、これは実験的なものであり、サポートされていないエンジンでポリフィルする必要があります。また、構文ではありません。
Estus Flask 2018

AbortControllerが何であるかを説明していないかもしれませんが(怠惰な人が簡単にできるように回答へのリンクを追加しました)、これはこれまでのところ最良の回答です。リクエストを単に無視するだけではまだ保留中ではありません。素晴らしい答え。
Aurelio 2018

2
「怠惰な人が簡単にできるように、答えにリンクを追加しました」-ルールtbhに従って、実際にはリンクと詳細情報が付属しているはずです。しかし、答えを改善してくれてありがとう。
ジェイウィック

7
人々が置かオフされているのでnitpickeryにより、TBHベター無回答よりも、この答えを持っている
マイケル・テリー

26

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

お役に立てば幸いです。


2
これは素晴らしいです!これは、他の回答で問題があったすべての厄介なエッジケースをカバーし、そして、あなたは明確な使用例を提供します。
AtteJuvonen20年

8

フェッチ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 2017年

1
ここでタイムアウトした後、リクエストはキャンセルされませんよね?これはOPにとっては問題ないかもしれませんが、クライアント側でリクエストをキャンセルしたい場合があります。
トリシス2018

2
@trysisええ、そうです。最近、AbortControllerを使用してフェッチを中止するためのソリューションを実装しましたが、ブラウザーのサポートが制限された実験的なものです。ディスカッション
code-jaff 2018

それは面白いです、IEとEdgeだけがそれをサポートしています!モバイルMozillaのサイトが再び動作している場合を除き...
trysis

Firefoxは57年からサポートしています。:: Chromeで見る::
Franklin Yu

6

編集:フェッチリクエストは引き続きバックグラウンドで実行され、コンソールにエラーが記録される可能性があります。

確かにPromise.raceアプローチはより良いです。

参照については、このリンクを参照してくださいPromise.race()

レースとは、すべてのプロミスが同時に実行され、プロミスの1つが値を返すとすぐにレースが停止することを意味します。したがって、1つの値のみが返されます。フェッチがタイムアウトした場合に呼び出す関数を渡すこともできます。

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

1

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(...)  

基になる接続を実際にキャンセルすることはありませんが、promiseをタイムアウトすることができます。
参照


1

コードでタイムアウトを設定していない場合は、ブラウザのデフォルトのリクエストタイムアウトになります。

1)Firefox-90秒

入力about:configFirefoxのURL欄に。キーに対応する値を見つけますnetwork.http.connection-timeout

2)クローム-300秒

ソース


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

これはstackoverflow.com/a/46946588/1008999とほとんど同じですが、デフォルトのタイムアウトがあります
Endless

-1

c-promise2 libを使用すると、タイムアウト付きのキャンセル可能なフェッチは次のようになります(Live 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

npmパッケージとしてのこのコードcp-fetch

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.