JavaScript Promise
インスタンスの.then
sをクリアする方法はありますか?
QUnit の上にJavaScriptテストフレームワークを作成しました。フレームワークは、Promise
で各テストを実行することにより、テストを同期的に実行します。 (このコードブロックの長さはごめんなさい。できる限りコメントしましたので、面倒ではありません。)
/* Promise extension -- used for easily making an async step with a
timeout without the Promise knowing anything about the function
it's waiting on */
$$.extend(Promise, {
asyncTimeout: function (timeToLive, errorMessage) {
var error = new Error(errorMessage || "Operation timed out.");
var res, // resolve()
rej, // reject()
t, // timeout instance
rst, // reset timeout function
p, // the promise instance
at; // the returned asyncTimeout instance
function createTimeout(reject, tempTtl) {
return setTimeout(function () {
// triggers a timeout event on the asyncTimeout object so that,
// if we want, we can do stuff outside of a .catch() block
// (may not be needed?)
$$(at).trigger("timeout");
reject(error);
}, tempTtl || timeToLive);
}
p = new Promise(function (resolve, reject) {
if (timeToLive != -1) {
t = createTimeout(reject);
// reset function -- allows a one-time timeout different
// from the one original specified
rst = function (tempTtl) {
clearTimeout(t);
t = createTimeout(reject, tempTtl);
}
} else {
// timeToLive = -1 -- allow this promise to run indefinitely
// used while debugging
t = 0;
rst = function () { return; };
}
res = function () {
clearTimeout(t);
resolve();
};
rej = reject;
});
return at = {
promise: p,
resolve: res,
reject: rej,
reset: rst,
timeout: t
};
}
});
/* framework module members... */
test: function (name, fn, options) {
var mod = this; // local reference to framework module since promises
// run code under the window object
var defaultOptions = {
// default max running time is 5 seconds
timeout: 5000
}
options = $$.extend({}, defaultOptions, options);
// remove timeout when debugging is enabled
options.timeout = mod.debugging ? -1 : options.timeout;
// call to QUnit.test()
test(name, function (assert) {
// tell QUnit this is an async test so it doesn't run other tests
// until done() is called
var done = assert.async();
return new Promise(function (resolve, reject) {
console.log("Beginning: " + name);
var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
$$(at).one("timeout", function () {
// assert.fail() is just an extension I made that literally calls
// assert.ok(false, msg);
assert.fail("Test timed out");
});
// run test function
var result = fn.call(mod, assert, at.reset);
// if the test returns a Promise, resolve it before resolving the test promise
if (result && result.constructor === Promise) {
// catch unhandled errors thrown by the test so future tests will run
result.catch(function (error) {
var msg = "Unhandled error occurred."
if (error) {
msg = error.message + "\n" + error.stack;
}
assert.fail(msg);
}).then(function () {
// resolve the timeout Promise
at.resolve();
resolve();
});
} else {
// if test does not return a Promise, simply clear the timeout
// and resolve our test Promise
at.resolve();
resolve();
}
}).then(function () {
// tell QUnit that the test is over so that it can clean up and start the next test
done();
console.log("Ending: " + name);
});
});
}
テストがタイムアウトした場合、タイムアウトPromiseはテストでassert.fail()
になり、テストは失敗としてマークされます。これはすべて正常で良好ですが、テストPromise(result
)がまだ解決を待っているため、テストは実行を続けます。
テストをキャンセルする良い方法が必要です。フレームワークモジュールthis.cancelTest
または何かにフィールドを作成し、テスト内で頻繁に(たとえば、各then()
反復の開始時に)キャンセルするかどうかを確認することでそれを行うことができます。ただし、理想的には、$$(at).on("timeout", /* something here */)
を使用してresult
変数の残りのthen()
sをクリアして、残りのテストが実行されないようにすることができます。
このようなものはありますか?
Promise.race([result, at.promise])
を使用してみました。うまくいきませんでした。
私のブロックを解除するために、テストアイデア内にmod.cancelTest
/pollingの行をいくつか追加しました。 (イベントトリガーも削除しました。)
return new Promise(function (resolve, reject) {
console.log("Beginning: " + name);
var at = Promise.asyncTimeout(options.timeout, "Test timed out.");
at.promise.catch(function () {
// end the test if it times out
mod.cancelTest = true;
assert.fail("Test timed out");
resolve();
});
// ...
}).then(function () {
// tell QUnit that the test is over so that it can clean up and start the next test
done();
console.log("Ending: " + name);
});
catch
ステートメントにブレークポイントを設定すると、ヒットします。今私を混乱させているのは、then()
ステートメントが呼び出されていないということです。アイデア?
最後のことを考え出した。 fn.call()
はエラーをスローしましたが、キャッチできなかったため、at.promise.catch()
が解決する前にテストプロミスが拒否されました。
JavaScript Promiseインスタンスの
.then
sをクリアする方法はありますか?
いいえ。少なくともECMAScript 6にはありません。 Promise(およびそのthen
ハンドラー)は、デフォルトではキャンセルできません(残念ながら)。 es-discussについては少し議論がありますが(例 here )、これを正しい方法で行う方法について議論しますが、勝つ方法はどれもES6にはありません。
現在の観点では、サブクラス化により、独自の実装を使用してキャンセル可能なプロミスを作成できるようになります(それがどの程度うまくいくかわからない)。
言語委員が最良の方法を見つけ出すまで(ES7できれば?)、ユーザーランドのPromise実装を使用できますが、その多くはキャンセル機能を備えています。
現在の議論は https://github.com/domenic/cancelable-promise および https://github.com/bergus/promise-cancellation ドラフトにあります。
ES6にはこれを行う標準的な方法はありませんが、これを処理する Bluebird というライブラリがあります。
また、反応ドキュメントの一部として説明されている推奨方法もあります。これは、2回目と3回目のアップデートの内容と似ています。
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then((val) =>
hasCanceled_ ? reject({isCanceled: true}) : resolve(val)
);
promise.catch((error) =>
hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
const cancelablePromise = makeCancelable(
new Promise(r => component.setState({...}}))
);
cancelablePromise
.promise
.then(() => console.log('resolved'))
.catch((reason) => console.log('isCanceled', reason.isCanceled));
cancelablePromise.cancel(); // Cancel the promise
から取得: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html
誰もPromise.race
をこの候補として挙げていないことに本当に驚いています:
const actualPromise = new Promise((resolve, reject) => { setTimeout(resolve, 10000) });
let cancel;
const cancelPromise = new Promise((resolve, reject) => {
cancel = reject.bind(null, { canceled: true })
})
const cancelablePromise = Object.assign(Promise.race([actualPromise, cancelPromise]), { cancel });
キャンセル可能なプロミス用のnpmライブラリがいくつかあります。
p-cancelablehttps://github.com/sindresorhus/p-cancelable
cancelable-promisehttps://github.com/alkemics/CancelablePromise
const makeCancelable = promise => {
let rejectFn;
const wrappedPromise = new Promise((resolve, reject) => {
rejectFn = reject;
Promise.resolve(promise)
.then(resolve)
.catch(reject);
});
wrappedPromise.cancel = () => {
rejectFn({ canceled: true });
};
return wrappedPromise;
};
使用法:
const cancelablePromise = makeCancelable(myPromise);
// ...
cancelablePromise.cancel();
約束の実行を停止することは実際には不可能ですが、拒否をハイジャックして約束自体から呼び出すことができます。
class CancelablePromise {
constructor(executor) {
let _reject = null;
const cancelablePromise = new Promise((resolve, reject) => {
_reject = reject;
return executor(resolve, reject);
});
cancelablePromise.cancel = _reject;
return cancelablePromise;
}
}
使用法:
const p = new CancelablePromise((resolve, reject) => {
setTimeout(() => {
console.log('resolved!');
resolve();
}, 2000);
})
p.catch(console.log);
setTimeout(() => {
p.cancel(new Error('Fucked up!'));
}, 1000);
シンプルバージョン:
リジェクト機能を与えるだけです。
function Sleep(ms,cancel_holder) {
return new Promise(function(resolve,reject){
var done=false;
var t=setTimeout(function(){if(done)return;done=true;resolve();}, ms);
cancel_holder.cancel=function(){if(done)return;done=true;if(t)clearTimeout(t);reject();}
})
}
ラッピングソリューション(工場)
私が見つけた解決策は、cancel_holderオブジェクトを渡すことです。キャンセル機能があります。キャンセル機能がある場合、キャンセル可能です。
このキャンセル関数は、Error( 'canceled')でプロミスを拒否します。
Resolve、reject、またはon_cancelの前に、キャンセル関数が理由なく呼び出されるのを防ぎます。
注入によるキャンセルアクションを渡すと便利だ
function cancelablePromise(cancel_holder,promise_fn,optional_external_cancel) {
if(!cancel_holder)cancel_holder={};
return new Promise( function(resolve,reject) {
var canceled=false;
var resolve2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; resolve.apply(this,arguments);}
var reject2=function(){ if(canceled) return; canceled=true; delete cancel_holder.cancel; reject.apply(this,arguments);}
var on_cancel={}
cancel_holder.cancel=function(){
if(canceled) return; canceled=true;
delete cancel_holder.cancel;
cancel_holder.canceled=true;
if(on_cancel.cancel)on_cancel.cancel();
if(optional_external_cancel)optional_external_cancel();
reject(new Error('canceled'));
};
return promise_fn.call(this,resolve2,reject2,on_cancel);
});
}
function Sleep(ms,cancel_holder) {
return cancelablePromise(cancel_holder,function(resolve,reject,oncacnel){
var t=setTimeout(resolve, ms);
oncacnel.cancel=function(){if(t)clearTimeout(t);}
})
}
let cancel_holder={};
// meanwhile in another place it can be canceled
setTimeout(function(){ if(cancel_holder.cancel)cancel_holder.cancel(); },500)
Sleep(1000,cancel_holder).then(function() {
console.log('sleept well');
}, function(e) {
if(e.message!=='canceled') throw e;
console.log('sleep interrupted')
})
ここに実装があります https://github.com/permettez-moi-de-construire/cancellable-promise
のように使用
const {
cancellablePromise,
CancelToken,
CancelError
} = require('@permettezmoideconstruire/cancellable-promise')
const cancelToken = new CancelToken()
const initialPromise = SOMETHING_ASYNC()
const wrappedPromise = cancellablePromise(initialPromise, cancelToken)
// Somewhere, cancel the promise...
cancelToken.cancel()
//Then catch it
wrappedPromise
.then((res) => {
//Actual, usual fulfill
})
.catch((err) => {
if(err instanceOf CancelError) {
//Handle cancel error
}
//Handle actual, usual error
})
どの
catch
呼び出し内でさらにキャンセルを行いましょうプルとコメントを歓迎します
これを試してください: https://github.com/dondevi/promise-abortable
const timeout = new AbortablePromise((resolve, reject, signal) => {
setTimeout(reject, timeToLive, error);
signal.onabort = resolve;
});
Promise.resolve(fn()).then(() => {
timeout.abort();
});
すべてのthen/catchの実行を停止する場合は、解決されないpromiseを注入することでこれを実行できます。おそらくメモリリークの再処理がありますが、問題は修正され、ほとんどのアプリケーションでメモリの浪費があまり発生しないはずです。
new Promise((resolve, reject) => {
console.log('first chain link executed')
resolve('daniel');
}).then(name => {
console.log('second chain link executed')
if (name === 'daniel') {
// I don't want to continue the chain, return a new promise
// that never calls its resolve function
return new Promise((resolve, reject) => {
console.log('unresolved promise executed')
});
}
}).then(() => console.log('last chain link executed'))
// VM492:2 first chain link executed
// VM492:5 second chain link executed
// VM492:8 unresolved promise executed
@Michael Yagudaevの答えは私にとって有効です。
しかし、元の答えは、拒否された処理を処理するためにラップされた約束を.catch()でつなげませんでした。@ Michael Yagudaevの答えの上に私の改善があります:
const makeCancelablePromise = promise => {
let hasCanceled = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise
.then(val => (hasCanceled ? reject({ isCanceled: true }) : resolve(val)))
.catch(
error => (hasCanceled ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled = true;
}
};
};
// Example Usage:
const cancelablePromise = makeCancelable(
new Promise((rs, rj) => {
/*do something*/
})
);
cancelablePromise.promise.then(() => console.log('resolved')).catch(err => {
if (err.isCanceled) {
console.log('Wrapped promise canceled');
return;
}
console.log('Promise was not canceled but rejected due to errors: ', err);
});
cancelablePromise.cancel();
Promiseで「キャンセル」プロパティを設定して、then()
およびcatch()
を早期に終了するように通知します。特に、onmessage
ハンドラーからPromisesに既存のマイクロタスクがキューに入れられているWebワーカーでは、非常に効果的です。
// Queue task to resolve Promise after the end of this script
const promise = new Promise(resolve => setTimeout(resolve))
promise.then(_ => {
if (promise.canceled) {
log('Promise cancelled. Exiting early...');
return;
}
log('No cancelation signaled. Continue...');
})
promise.canceled = true;
function log(msg) {
document.body.innerHTML = msg;
}
PがPromiseを含む変数である場合、p.then(empty);
は、最終的に完了するか、すでに完了している場合に、約束を破棄する必要があります(はい、これは元の質問ではありませんが、私の質問です)。 「空」はfunction empty() {}
です。私は単なる初心者で、おそらく間違っていますが、これらの他の答えは複雑すぎるようです。約束は単純であるはずです。