私は完了する必要がある2つの非同期タスクを持っています-それらが「パイを作る」(make
)と「パイを焼く」(bake
)だと言います。私はまた、すべてが終わった後、「台所を掃除する」(cleanup
)を掃除する必要があります。 bake
はmake
に依存しているので、物事が完了したらcleanup
にしたいです。ただし、いくつかの複雑な問題があります。
made
持っている場合、私がしなければならないことはbake
だけです。make
の間に問題が発生した場合-このパイ事業全体に飽き飽きしている可能性があります-cleanup
する必要があります。bake
の間に問題が発生した場合-オーブンが爆発します-cleanup
も必要です。最初のパスは次のようになります。
func pieTime()
if !pie.isMade()
make()
else if !pie.isBaked()
bake()
else
cleanup(null)
func make()
makeThePie(completion: {
if pie.hasError()
cleanup(error)
else
bake()
})
func bake()
bakeThePie(completion: {
if pie.hasError()
cleanup(error)
else
cleanup(null)
})
func cleanup(error)
if error != null
shout("what the ***: %s", error.string())
destroy(pie)
これは悪くありませんが、このアプローチの私の主な問題は、フローからの単一の出口点がないことです。 cleanup
はたくさんの場所から呼び出されます。エラー処理がより複雑になり、非同期呼び出しをチェーンに追加するにつれて、追跡するフローからの出口点がますます多くなります。それらのいずれかでcleanup
に電話できなかった場合、その結果、使用できないキッチンになります。
基本的に、私は非同期呼び出しのdo/catch/finallyのようなものを探しています。私が思いつくことができる唯一のものはキューです-必要なタスクのみをキューに入れ、最後にクリーンアップをキューに入れます-しかし、このインスタンスのような2つのタスクだけでは少し重すぎるようです。この問題に定評のあるパターンはありますか?
慣用的な q
1のような約束チェーンを使用できます。
doSomethingThatReturnsAPromise
.then(function (result, error) {
if (error) {
[the previous command failed in some way; handle it or throw.]
}
})
.then([same again])
[…]
.finally(function() {
[global cleanup]
})
呼び出しのツリーとの主な違いは、追跡するのが非常に直感的であるということです。エントリポイントが何であるかは問題ではなく、ステップのシーケンスは問題ではありません。クリーンアップは、シーケンスが完了すると無条件に行われます。
何故なの
bake
への呼び出しなしで関数make
およびcleanUp
を実装します(ただし、特定の例外のような一貫したエラー信号)
関数ラッパー(「デコレータ」)を実装します。これは、任意の関数をパラメーターとして取り、非同期に実行し、エラー例外をキャッチして、それに応じてcleanup
を呼び出しますか?
次に、bake
およびmake
(および他の同等のタスク)が開始されるコード内の場所は、ラッパーを使用してタスクを実行するだけです。最後のcleanup(null)
もbake
によって直接呼び出されるのではなく、呼び出し元によって呼び出されます(たとえば、エラーなしに完了したすべてのタスクのイベントを取得したとき)。
Cleanupメソッドは、makeおよびbakeコマンドチェーンのデコレータとして機能します。この場合、デコレータはクリーンアップの目的を果たします。コマンドチェーンを実行すると、クリーンアップがキャッチしてクリーンアップするまで、エラーがチェーンを上に移動します。
ベイクは作成に依存し、エラー処理も作成の結果に依存するため、このチェーンの一部として非同期で追加する理由はありません。チェーン全体を非同期で実行する場合(複数のパイを一度に作成してベイク処理する場合)、最上部で非同期コールアウトを作成し、個々のコールアウトではなくコールチェーン全体のタスク結果を確認します。
これがチェーンです(C#の場合):
var command = new PieCleanup(new MakePie(new BakePie()));
//You could run this async if many pies to make and bake
command.Execute(new Pie());
クリーンアップデコレータ:
public class PieCleanup : ICommand<Pie>
{
private readonly ICommand<Pie> _command;
public PieCleanup(ICommand<Pie> command)
{
_command = command;
}
public void Execute(Pie pie)
{
try
{
_command.Execute(pie);
}
catch (Exception)
{
//TODO: Cleanup
}
}
}
パイを作る:
public class MakePie : ICommand<Pie>
{
private readonly ICommand<Pie> _command;
public MakePie(ICommand<Pie> command)
{
_command = command;
}
public void Execute(Pie pie)
{
//TODO: Make Pie
//Now Run the next command (bake)
_command.Execute(pie);
}
}
パイを焼く:
public class BakePie : ICommand<Pie>
{
public void Execute(Pie pie)
{
//TODO: Bake Pie
}
}