JavaScript: タイムアウトした処理にエラーとして扱う
HTTP リクエストのように処理が一定の時間を超える場合、エラーとして扱う必要がある場合、 Promise.race を使う。Bluebird を使ったコードの例は次のとおり。
const Bluebird = require('bluebird'); const p1 = Bluebird.resolve('成功').delay(500); const p2 = Bluebird.reject(new Error('失敗')).delay(800); Bluebird.race([p1, p2]) .then(value => { console.log(value); }) .catch(value => { console.log(value); });
timeout メソッドで処理時間の上限を指定することができます。
const Promise = require("bluebird"); const p1 = Promise.resolve("成功").delay(300); p1.timeout(100) .then((value) => { console.log(value); }).catch(Promise.TimeoutError, (e) => { console.log("エラー"; });
ES2015 Promise の場合、Promise.race を使うことができます。
const p1 = new Promise((resolve, reject) => { setTimeout((value) => { console.log("p1 を実行してます。"); resolve(value); }, 700, "成功"); }); const p2 = new Promise((resolve, reject) => { setTimeout((value) => { console.log("p2 を実行しています。"); reject(value); }, 500, "失敗"); }); Promise.race([p1, p2]) .then((value) => { console.log(value); }) .catch((value) => { console.log(value); });
毎回エラー処理を書かなくてすむようにタイムアウトを指定できる Promise を返す関数を定義してみましょう。
function timeout(p, ms) { const wait = new Promise((resolve, reject) => { setTimeout(() => { console.log("エラー処理をしています。"); reject(new Error("エラー")); }, ms); }); return Promise.race([p, wait]); } const p = new Promise((resolve, reject) => { setTimeout((value) => { console.log("p1 を実行してます。"); resolve(value); }, 2000, "成功"); }); timeout(p, 3000) .then((value) => { console.log(value); }) .catch((value) => { console.log(value); });
追記。Promise.race を使わなくてもタイムアウトは実現できます。
// https://github.com/github/fetch/issues/175#issuecomment-284787564 function timeout(promise, ms) { return new Promise((resolve, reject) => { setTimeout(() => { console.log("タイムアウトを処理しています。"); reject(new Error("timeout")) }, ms); promise.then(resolve, reject); }) } const p = new Promise((resolve, reject) => { setTimeout((value) => { console.log("p1 を実行してます。"); resolve(value); }, 2000, "成功"); }); timeout(p, 1000) .then((value) => { console.log(value); }) .catch((value) => { console.log(value); });
最初に resolve もしくは reject されたものが考慮され、そのほかは無視されることは次のコードでも確認できます。
const p = new Promise((resolve, reject) => { setTimeout((value) => { console.log("resolve を実行しています。"); resolve(value); }, 1000, "成功"); setTimeout((value) => { console.log("resolve2 を実行しています。"); resolve(value); }, 2000, "成功2"); setTimeout((value) => { console.log("reject を実行しています。"); resolve(value); }, 3000, "失敗"); }); p.then((value) => { console.log(value); }) .catch((value) => { console.log(value); });