更新: jQuery 3.0 では、以下に概説する問題が修正されました。Promises/A+ に真に準拠しています。
はい、jQuery の promise には深刻な固有の問題があります。
とは言うものの、この記事が書かれて以来、jQuery はより多くの Promises/Aplus の苦情になるように多大な努力を払い、現在では連鎖する .then メソッドを持っています。
したがってreturnsPromise().then(a).then(b)
、プロミスを返す関数のjQueryでも、期待どおりに機能a
しb
、戻り値をアンラップしてから先に進みます。このフィドルに示されているように:
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
timeout().then(function(){
document.body.innerHTML = "First";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Second";
return timeout();
}).then(function(){
document.body.innerHTML += "<br />Third";
return timeout();
});
ただし、jQueryの 2 つの大きな問題は、エラー処理と予期しない実行順序です。
エラー処理
catch とは異なり、解決したとしても、拒否された jQuery promise を「処理済み」としてマークする方法はありません。これにより、jQuery での拒否は本質的に壊れており、使用が非常に困難になり、 synchronous とは異なりtry/catch
ます。
ここに何のログがあると思いますか? (フィドル)
timeout().then(function(){
throw new Error("Boo");
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
"uncaught Error: boo"
あなたが正しいと推測した場合。jQuery promise はスローセーフではありません。Promises/Aplus promise とは異なり、スローされたエラーを処理することはできません。リジェクトの安全性はどうですか?(フィドル)
timeout().then(function(){
var d = $.Deferred(); d.reject();
return d;
}).then(function(){
console.log("Hello World");
},function(){
console.log("In Error Handler");
}).then(function(){
console.log("This should have run");
}).fail(function(){
console.log("But this does instead");
});
次のログ"In Error Handler" "But this does instead"
- jQuery promise の拒否を処理する方法はまったくありません。これは、期待するフローとは異なります。
try{
throw new Error("Hello World");
} catch(e){
console.log("In Error handler");
}
console.log("This should have run");
これは、Bluebird や Q などの Promises/A+ ライブラリで得られるフローであり、有用性に期待するものです。これは非常に大きく、スローの安全性はプロミスの大きなセールス ポイントです。この場合、Bluebirdは正しく動作しています。
実行順序
jQuery は、基になる promise が既に解決されている場合、渡された関数を延期するのではなく、すぐに実行します。そのため、ハンドラーをアタッチしている promise が既に解決されているかどうかに応じて、コードの動作が異なります。これは事実上Zalgoを解放しており、最も厄介なバグのいくつかを引き起こす可能性があります。これにより、デバッグが最も困難なバグがいくつか発生します。
次のコードを見ると: ( fiddle )
function timeout(){
var d = $.Deferred();
setTimeout(function(){ d.resolve(); },1000);
return d.promise();
}
console.log("This");
var p = timeout();
p.then(function(){
console.log("expected from an async api.");
});
console.log("is");
setTimeout(function(){
console.log("He");
p.then(function(){
console.log("̟̺̜̙͉Z̤̲̙̙͎̥̝A͎̣͔̙͘L̥̻̗̳̻̳̳͢G͉̖̯͓̞̩̦O̹̹̺!̙͈͎̞̬ *");
});
console.log("Comes");
},2000);
非常に危険な動作であることsetTimeout
がわかります。元のタイムアウトが終了するのを待つため、jQuery はその実行順序を切り替えます。なぜなら... スタック オーバーフローを引き起こさない決定論的 API が好きな人がいるでしょうか? これが、Promises/A+ 仕様で、Promise が常にイベント ループの次の実行まで延期されることを要求する理由です。
サイドノート
.done
Bluebird (および実験的に When) のような新しくて強力なプロミス ライブラリは、Q のようにチェーンの最後で必要としないことに言及する価値があります。