web-dev-qa-db-ja.com

QPromisesでthen関数をスキップする適切な方法

私のコードでは、特定の条件に基づいて、すべてのdone関数に関係なく、then関数にスキップしたいと思います。

この質問の元のバージョンは編集中です。以下は私が扱っている実際の問題です。ご不便をおかけして申し訳ありません

実際の問​​題:

ファイルを読み取って処理しています。ファイルの内容が特定の条件に一致する場合は、ファイルシステムで一連の操作(たとえば、いくつかのファイルの読み取りと書き込み)を実行してから、done関数を実行する必要があります。条件が満たされない場合、一連の操作をすべてスキップし、done関数を直接実行する必要があります

すべてのresult関数でオブジェクト(たとえばthen)を返し、次のthenresultを更新して返します。したがって、すべてのthenが完了すると、doneには累積されたresultが含まれます。最後に、doneresultを処理して出力します。

したがって、最初に条件が満たされない場合、doneは単にresultを出力します(これは空になります)。

Q()
.then(readFile)
.then(function (contents) {
    var processResult = process the contents;
    if (processResult) {
        return {};
    } else {
        // How can I skip to `done` from here
    }
})
.then(function (results) {
    // do some more processing and update results
    return results;
})
...   // Few more then functions similar to the one above
...
.fail(function (exception) {
    console.error(exception.stack);
})
.done(function (results) {
   // do some more processing and update results
   console.log(results);
});
26
thefourtheye

スキップする条件、実行している操作の種類、および条件が失敗したときに全体がどれだけ「役立つ」かによって少し異なります。ここでスマートな拒否を使用して、メッセージを伝えることができる場合があります。そうでなければ、これに対処する正しい方法は、実際にはネストされた一連のpromise呼び出しであると私は信じています。

これは、 promises の背後にあるコアアイデアとも一致します。これは、同期制御構造を非同期コード実行に戻すことです。一般に、promiseを使用するときは、最初にタスク同期コードをどのように実行するかを検討する必要があります。そして、あなたがあなたの状況について考えるならば、それはおそらくこのように働くでしょう:

var contents = readFromFile();
var results = initialOperation(contents);
if (fancyCondition(results)) {
     results = doSomething(results);
     results = doMore(results);
}
processAndPrint(results);

したがって、同期コードには実際のブランチがあります。そのため、promiseを使用する非同期コードでそれを回避したいのは意味がありません。skipのことだけができれば、基本的にはgotoでジャンプを使用していました。しかし、代わりに、あなたは分岐して、他のいくつかのことを別々に行います。

したがって、promiseと非同期コードに戻ると、チェーン操作の別のセットを持つ実際のブランチを持つことは完全に問題なく、promiseの背後にある意図の精神で実際に行われます。したがって、上記のコードは次のようになります。

readFromFile(fileName)
.then(initialOperation)
.then(function (results) {
    if (fancyCondition(results) {
        return doSomething(results)
            .then(doMore);
    }
    return results;
})
.catch(errorHandler)
.then(processResults)
.then(outputResults); // Or `done` in Q

また、promiseパイプラインは、thenからインラインで作成するのではなく、promiseを独自に返す関数を使い始めると、自動的にはるかにきれいに見えることに注意してください。

24
poke

しかし、それから私たちはthenable関数をネストしています。これは、promiseを使用して最初に避けたかったことです。

いいえ、これは確かに適切な方法です。 branchingの場合、常に追加レベルのネストが必要になります。インデントが大きくなりすぎている場合でも、サブプロシージャを呼び出すという古いトリックを適用できます(これはコールバック関数のネスト解除にも使用されます)。


他の解決策はかなり醜いです。チェーン内のいくつかのthenハンドラーをスキップすることは、より深いネストなしで、例外をスローしてpromiseを拒否することで実行できます。これは最終的には捕らえられる可能性があります。いくつかのシナリオに当てはまるかもしれませんが、これは良い考えではないと思います。

私が考えることができるもう1つの方法は、条件付きの結果を別のデータ構造にラップすることです。これは、thensのチェーンを通過する可能性があります。これは、HaskellのMaybeまたはScalaのOptionのようになり、ハンドラーでそれらをmapします。ただし、これには追加レベルのネストも必要であり、チェーン全体に何も明示的に伝播する効率が低下し、チェーン内で返されるpromiseに問題が発生します。

9
Bergi

「スキップ」を正しく理解している場合、一般的な解決策は「スキップ」条件下で値を返さないことです。これにより、入力値が透過的に通過できるようになります。

例えば:

...then(function (randomInteger) {
    var remainder = randomInteger % 2;
    console.log(['Even','Odd'][remainder] + ' number: ', randomInteger);
    if(remainder) {
        return randomInteger + 1;
    }
})...
1

コンテキスト変数を使用して、promiseチェーン内で呼び出される関数に条件付きロジックを実装します。各関数は同じコンテキスト変数を返します。これにより、プロミスチェーンが整然と保たれます。条件付きテストを示唆する関数名を使用すると、読みやすさが向上します。

function processFirstPass(context) {
    var processResult = process the contents;
    if (processResult) {
        context.result = processResult;
    }
    context.specialTag = ! processResult;
    return context;
}

processForOnlySpecialTag(context) {
    if (context.specialTag) {
        context.result = // do some more processing and update results
    }
    return context;
}

function process() {
    var context = { filename: 'input.txt' };
    readFromFile(context)
    .then(procesFirstPass)
    .then(processForOnlySpecialTag)
    .fail(function (exception) {
        console.error(exception.stack);
    })
    .done(function (context) {
       // do some more processing and update results
       console.log(context.result);
    });
}
0
Tongfa