promiseチェーンを解除し、それが解除されたチェーン内のステップに基づいて関数を呼び出します(拒否)


135

更新:

この投稿の将来の視聴者を支援するために、私はこのplumaの回答のデモを作成しました

質問:

私の目標はかなり簡単なようです。

  step(1)
  .then(function() {
    return step(2);
  }, function() {
    stepError(1);
    return $q.reject();
  })
  .then(function() {

  }, function() {
    stepError(2);
  });

  function step(n) {
    var deferred = $q.defer();
    //fail on step 1
    (n === 1) ? deferred.reject() : deferred.resolve();
    return deferred.promise;
  }
  function stepError(n) {
    console.log(n); 
  }

ここでの問題は、ステップ1で失敗すると、stepError(1)ANDとANDの両方stepError(2)が実行されることです。私はしていない場合はreturn $q.reject、その後stepError(2)解雇されることはありませんが、step(2)、私は理解している、意志。私がやろうとしていること以外はすべて達成しました。

エラーチェーン内のすべての関数を呼び出さずに、拒否時に関数を呼び出すことができるように、プロミスをどのように記述しますか?またはこれを達成する別の方法はありますか?

こちらがライブデモですがありますので作業できます。

更新:

私はそれをちょっと解決しました。ここでは、チェーンの最後でエラーをキャッチしてデータを渡しているreject(data)ので、エラー関数で処理する問題がわかります。データに依存したくないので、これは実際には私の要件を満たしていません。それは不十分ですが、私の場合、返されたデータに依存して何をすべきかを決定するのではなく、エラーコールバックを関数に渡す方がクリーンです。

こちらのライブデモ(クリック)。

step(1)
  .then(function() {
    return step(2);
  })
  .then(function() {
    return step(3);
  })
  .then(false, 
    function(x) {
      stepError(x);
    }
  );
  function step(n) {
    console.log('Step '+n);
    var deferred = $q.defer();
    (n === 1) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
  }
  function stepError(n) {
    console.log('Error '+n); 
  }

1
これは、任意のより複雑になった場合、非同期JavaScriptのlibのことかもしれないのヘルプがあります
lucuma

Promise.prototype.catch()MDNの例は、まったく同じ問題の解決策を示しています。
toraritte 2018

回答:


199

コードが期待どおりに動作しない理由は、実際にはコードが思っていることとは異なることをしているためです。

次のようなものがあるとします。

stepOne()
.then(stepTwo, handleErrorOne)
.then(stepThree, handleErrorTwo)
.then(null, handleErrorThree);

何が起こっているのかをよりよく理解するために、これがtry/ catchブロックを含む同期コードであるとしましょう:

try {
    try {
        try {
            var a = stepOne();
        } catch(e1) {
            a = handleErrorOne(e1);
        }
        var b = stepTwo(a);
    } catch(e2) {
        b = handleErrorTwo(e2);
    }
    var c = stepThree(b);
} catch(e3) {
    c = handleErrorThree(e3);
}

onRejectedハンドラ(の第2引数はthen)実質的に(のような誤り訂正機構であるcatchブロック)。エラーがにスローさhandleErrorOneれると、次のキャッチブロック(catch(e2))によってキャッチされます。

これは明らかに意図したものではありません。

何が問題があっても、解決チェーン全体が失敗したいとしましょう:

stepOne()
.then(function(a) {
    return stepTwo(a).then(null, handleErrorTwo);
}, handleErrorOne)
.then(function(b) {
    return stepThree(b).then(null, handleErrorThree);
});

注:が拒否handleErrorOneされた場合にのみ呼び出されるため、そのままにすることができますstepOne(これはチェーンの最初の関数なので、この時点でチェーンが拒否された場合は、その関数のpromiseが原因である可能性があります) 。

重要な変更は、他の関数のエラーハンドラーがメインのプロミスチェーンの一部ではないことです。代わりに、各ステップには独自の「サブチェーン」があり、onRejectedそのステップが拒否された場合にのみ呼び出されます(ただし、メインチェーンから直接到達することはできません)。

これが機能する理由は、onFulfilledonRejectedthenメソッドへのオプションの引数であるためです。promiseが実行(つまり解決)さthenれ、チェーンの次のものがonFulfilledハンドラーを持たない場合、そのようなハンドラーを持つものが存在するまでチェーンは継続します。

つまり、次の2行は同等です。

stepOne().then(stepTwo, handleErrorOne)
stepOne().then(null, handleErrorOne).then(stepTwo)

ただし、次の行は上記の2つと同等ではありません

stepOne().then(stepTwo).then(null, handleErrorOne)

Angularのpromiseライブラリ$qはkriskowalのQライブラリに基づいています(豊富なAPIを備えていますが、で見つけることができるすべてが含まれています$q)。GitHubにあるQのAPIドキュメントが役立つかもしれません。Qは、Promises / A +仕様を実装しています。then、Promise解決動作が、そして正確。

編集:

また、エラーハンドラーのチェーンから抜け出したい場合は、拒否されたプロミスを返すか、エラーをスローする必要があります(自動的にキャッチされ、拒否されたプロミスにラップされます)。プロミスを返さない場合はthen、戻り値を解決プロミスにラップします。

これは、何も返さない場合、値の解決された約束を効果的に返すことを意味しますundefined


138
この部分はゴールドです。if you don't return anything, you are effectively returning a resolved promise for the value undefined.ありがとう@pluma
Valerio、

7
これは確かです。私はそれをそれに値する大胆さを与えるために編集しています
Cyril CHAPON

拒否は現在の機能を終了しますか?たとえば、rejectが最初に呼び出された場合はresolveは呼び出されません `if(bad){reject(status); } resolve(results); `
SuperUberDuper

stepOne().then(stepTwo, handleErrorOne) `stepOne()。then(null、handleErrorOne).then(stepTwo)`これらは完全に同等ですか?stepOneコードの2行目が拒否された場合は実行されますstepTwoが、最初の行は実行されhandleErrorOneて停止するだけです。それとも何か不足していますか?
JeFf 2016年

5
質問に対する明確な解決策は実際には提供されていませんが、それでも適切な説明
Yerken

57

パーティーには少し遅れましたが、この簡単な解決策は私にとってうまくいきました:

function chainError(err) {
  return Promise.reject(err)
};

stepOne()
.then(stepTwo, chainError)
.then(stepThreee, chainError);

これは、あなたがすることができます破るチェーンのうち。


1
お世話になったが、FYI、あなたのようなキャッチで抜け出すために、その後にそれを返すことができます:.then(user => { if (user) return Promise.reject('The email address already exists.') })
クレイグ・バンTonderの

1
@CraigvanTonderあなたは約束の範囲内で投げることができ、それはあなたのコードと同じように機能します:.then(user => { if (user) throw 'The email address already exists.' })
Francisco Presencia

1
これが唯一の正解です。そうでない場合、ステップ1にエラーがあっても、ステップ3は実行されます。
wdetac 2017

1
明確にするために、エラーがstepOne()で発生した場合、両方のchainErrorが正しく呼び出されますか?これが望ましい場合。私はこれを行うスニペットを持っていますが、何かを誤解しているかどうかはわかり
user320550

10

必要なのは繰り返し .then()開始する特殊なケースと終了する特殊なケースを持つチェーンです。

ここでのコツは、失敗ケースのステップ番号を取得して、最終的なエラーハンドラーに波及させることです。

  • 開始:電話 step(1)無条件に。
  • 繰り返しパターン:チェーンA .then()次のコールバックで:
    • 成功:step(n + 1)を呼び出します
    • 失敗:以前の据え置きが拒否された値をスローするか、エラーを再スローします。
  • 終了:.then()成功ハンドラーなしのaと最終エラーハンドラーをチェーンします。

全部書いても構いませんが、名前付きの一般化された関数を使ってパターンを説明する方が簡単です。

function nextStep(n) {
    return step(n + 1);
}

function step(n) {
    console.log('step ' + n);
    var deferred = $q.defer();
    (n === 3) ? deferred.reject(n) : deferred.resolve(n);
    return deferred.promise;
}

function stepError(n) {
    throw(n);
}

function finalError(n) {
    console.log('finalError ' + n);
}
step(1)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(nextStep, stepError)
    .then(null, finalError);});

デモを見る

どのようにstep()で、遅延オブジェクトがで拒否または解決され、チェーンn内の次のコールバックでその値を使用できるようになるかに注意してください.then()。一度stepError呼ばれ、それがによって処理されるまで、エラーが繰り返し再スローされますfinalError


有益な回答なので、保持する価値がありますが、私が直面している問題ではありません。私の投稿でこの解決策について言及しましたが、私が探しているものではありません。投稿の上部にあるデモをご覧ください。
m59

1
m59、これは、「エラーチェーン内のすべての関数を呼び出さずに、拒否時に関数を呼び出すことができるように、どのようにプロミスを書くのですか」という質問に対する回答です。そして、質問のタイトル、「プロミスチェーンを
解除し

そうです、私が言ったように、それは有益であり、私はこのソリューションを私の投稿に含めました(詳細は少なくしました)。このアプローチは、チェーンを継続できるように修正することを目的としています。それ私が探しているものを達成することができますが、受け入れられた答えのアプローチほど自然ではありません。つまり、タイトルと質問の内容をやりたい場合は、Plumaのアプローチを使用します。
m59

7

拒否する場合は、拒否エラーを渡し、ステップエラーハンドラーを関数にラップして、拒否を処理するか、チェーンの最後まで「再スロー」するかをチェックします。

// function mocking steps
function step(i) {
    i++;
    console.log('step', i);
    return q.resolve(i);
}

// function mocking a failing step
function failingStep(i) {
    i++;
    console.log('step '+ i + ' (will fail)');
    var e = new Error('Failed on step ' + i);
    e.step = i;
    return q.reject(e);
}

// error handler
function handleError(e){
    if (error.breakChain) {
        // handleError has already been called on this error
        // (see code bellow)
        log('errorHandler: skip handling');
        return q.reject(error);
    }
    // firs time this error is past to the handler
    console.error('errorHandler: caught error ' + error.message);
    // process the error 
    // ...
    //
    error.breakChain = true;
    return q.reject(error);
}

// run the steps, will fail on step 4
// and not run step 5 and 6
// note that handleError of step 5 will be called
// but since we use that error.breakChain boolean
// no processing will happen and the error will
// continue through the rejection path until done(,)

  step(0) // 1
  .catch(handleError)
  .then(step) // 2
  .catch(handleError)
  .then(step) // 3
  .catch(handleError)
  .then(failingStep)  // 4 fail
  .catch(handleError)
  .then(step) // 5
  .catch(handleError)
  .then(step) // 6
  .catch(handleError)
  .done(function(){
      log('success arguments', arguments);
  }, function (error) {
      log('Done, chain broke at step ' + error.step);
  });

コンソールに表示されるもの:

step 1
step 2
step 3
step 4 (will fail)
errorHandler: caught error 'Failed on step 4'
errorHandler: skip handling
errorHandler: skip handling
Done, chain broke at step 4

ここにいくつかの作業コードがあり ますhttps://jsfiddle.net/8hzg5s7m/3/

各ステップに特定の処理がある場合、ラッパーは次のようになります。

/*
 * simple wrapper to check if rejection
 * has already been handled
 * @param function real error handler
 */
function createHandler(realHandler) {
    return function(error) {
        if (error.breakChain) {
            return q.reject(error);
        }
        realHandler(error);
        error.breakChain = true;
        return q.reject(error);    
    }
}

それからあなたの鎖

step1()
.catch(createHandler(handleError1Fn))
.then(step2)
.catch(createHandler(handleError2Fn))
.then(step3)
.catch(createHandler(handleError3Fn))
.done(function(){
    log('success');
}, function (error) {
    log('Done, chain broke at step ' + error.step);
});

2

私が正しく理解していれば、失敗したステップのエラーのみを表示しますか?

これは、最初の約束の失敗例をこれに変更するのと同じくらい簡単なはずです。

step(1).then(function (response) {
    step(2);
}, function (response) {
    stepError(1);
    return response;
}).then( ... )

$q.reject()最初のステップの失敗の場合に戻ることで、その約束を拒否し、errorCallbackが2番目に呼び出されるようにしthen(...)ます。


一体何をやっているんだ... 私の投稿で私がそれを試したのを見てください、しかし、チェーンはキックバックして走りstep(2)ます。今、私はそれをもう一度試してみましたが、起こっていません。私は困惑している。
m59

1
あなたがそれを言ったことを私は見ました。それは奇妙です。を含むその関数は、正常return step(2);step(1)解決されたときにのみ呼び出されます。
ザイン2013

スクラッチ-それは間違いなく起こっています。投稿で言ったように、を使用しない場合return $q.reject()、チェーンは継続します。この場合、return responseそれを台無しにしました。これを参照してください:jsbin.com/EpaZIsIp/6/edit
m59 '21

うーん、いいよ。私が変更したときに投稿したjsbinで動作するようですが、何かを見落としていたに違いありません。
ザイン

ええ、間違いなく今は機能していないようです。私のための描画ボードに戻ります!
ザイン

2
var s = 1;
start()
.then(function(){
    return step(s++);
})
.then(function() {
    return step(s++);
})
.then(function() {
    return step(s++);
})
.then(0, function(e){
   console.log(s-1); 
});

http://jsbin.com/EpaZIsIp/20/edit

または、任意の数のステップを自動化します。

var promise = start();
var s = 1;
var l = 3;
while(l--) {
    promise = promise.then(function() {
        return step(s++);
    });
}
promise.then(0, function(e){
   console.log(s-1); 
});

http://jsbin.com/EpaZIsIp/21/edit


しかし、私が呼び出す場合deferred.reject(n)、nonErrorオブジェクトでプロミスが拒否されたという警告が表示されます
9me

2

libsのようにroを使用してみてください。

https://www.npmjs.com/package/promise-chain-break

    db.getData()
.then(pb((data) => {
    if (!data.someCheck()) {
        tellSomeone();

        // All other '.then' calls will be skiped
        return pb.BREAK;
    }
}))
.then(pb(() => {
}))
.then(pb(() => {
}))
.catch((error) => {
    console.error(error);
});

2

async / awaitを使用してこの問題を解決する場合:

(async function(){    
    try {        
        const response1, response2, response3
        response1 = await promise1()

        if(response1){
            response2 = await promise2()
        }
        if(response2){
            response3 = await promise3()
        }
        return [response1, response2, response3]
    } catch (error) {
        return []
    }

})()

1

エラーハンドラーを個別のチェーン要素として、ステップの実行に直接アタッチします。

        // Handle errors for step(1)
step(1).then(null, function() { stepError(1); return $q.reject(); })
.then(function() {
                 // Attach error handler for step(2),
                 // but only if step(2) is actually executed
  return step(2).then(null, function() { stepError(2); return $q.reject(); });
})
.then(function() {
                 // Attach error handler for step(3),
                 // but only if step(3) is actually executed
  return step(3).then(null, function() { stepError(3); return $q.reject(); });
});

または使用catch()

       // Handle errors for step(1)
step(1).catch(function() { stepError(1); return $q.reject(); })
.then(function() {
                 // Attach error handler for step(2),
                 // but only if step(2) is actually executed
  return step(2).catch(function() { stepError(2); return $q.reject(); });
})
.then(function() {
                 // Attach error handler for step(3),
                 // but only if step(3) is actually executed
  return step(3).catch(function() { stepError(3); return $q.reject(); });
});

注:これは基本的に、Plumaが彼の回答で示唆しているパターンと同じですが、OPの名前を使用しています。


1

発見Promise.prototype.catch()MDNの例を以下には非常に役立ちます。

(受け入れられた回答then(null, onErrorHandler)は、基本的にと同じであると述べていcatch(onErrorHandler)ます。)

catchメソッドの使用とチェーン

var p1 = new Promise(function(resolve, reject) {
  resolve('Success');
});

p1.then(function(value) {
  console.log(value); // "Success!"
  throw 'oh, no!';
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

// The following behaves the same as above
p1.then(function(value) {
  console.log(value); // "Success!"
  return Promise.reject('oh, no!');
}).catch(function(e) {
  console.log(e); // "oh, no!"
}).then(function(){
  console.log('after a catch the chain is restored');
}, function () {
  console.log('Not fired due to the catch');
});

エラーをスローするときの注意点

// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
  throw 'Uh-oh!';
});

p1.catch(function(e) {
  console.log(e); // "Uh-oh!"
});

// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    throw 'Uncaught Exception!';
  }, 1000);
});

p2.catch(function(e) {
  console.log(e); // This is never called
});

// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
  resolve();
  throw 'Silenced Exception!';
});

p3.catch(function(e) {
   console.log(e); // This is never called
});

解決した場合

//Create a promise which would not call onReject
var p1 = Promise.resolve("calling next");

var p2 = p1.catch(function (reason) {
    //This is never called
    console.log("catch p1!");
    console.log(reason);
});

p2.then(function (value) {
    console.log("next promise's onFulfilled"); /* next promise's onFulfilled */
    console.log(value); /* calling next */
}, function (reason) {
    console.log("next promise's onRejected");
    console.log(reason);
});

1

最良の解決策は、ES6を使用するためにプロミスチェーンにリファクタリングすることです。その後、関数から戻るだけで、残りの動作をスキップできます。

私はこのパターンに1年以上頭をぶつけており、awaitの使用は天国です。


純粋なIEを使用する場合、非同期/待機はサポートされません。
ndee

0

SequentialPromiseモジュールを使用する

意図

通常の方法で各操作の現在のインデックスを追跡しながら、要求を順次実行する責任があるモジュールを提供します。コマンドパターンで操作を定義するで柔軟性を高めます。

参加者

  • 環境:メンバーメソッドが操作を実行するオブジェクト。
  • SequentialPromiseexecute各操作をチェーンおよび追跡するメソッドを定義します。SequentialPromiseは、実行されたすべての操作からPromise-Chainを返します。
  • Invoker:SequentialPromiseインスタンスを作成し、コンテキストとアクションを提供しexecute、各操作のオプションの順序リストを渡しながらそのメソッドを呼び出します。

結果

Promise解決の通常の動作が必要な場合は、SequentialPromiseを使用します。SequentialPromiseは、Promiseが拒否されたインデックスを追跡します。

実装

clear();

var http = {
    get(url) {
        var delay = Math.floor( Math.random() * 10 ), even = !(delay % 2);
        var xhr = new Promise(exe);

        console.log(`REQUEST`, url, delay);
        xhr.then( (data) => console.log(`SUCCESS: `, data) ).catch( (data) => console.log(`FAILURE: `, data) );

        function exe(resolve, reject) {
            var action = { 'true': reject, 'false': resolve }[ even ];
            setTimeout( () => action({ url, delay }), (1000 * delay) );
        }

        return xhr;
    }
};

var SequentialPromise = new (function SequentialPromise() {
    var PRIVATE = this;

    return class SequentialPromise {

        constructor(context, action) {
            this.index = 0;
            this.requests = [ ];
            this.context = context;
            this.action = action;

            return this;
        }

        log() {}

        execute(url, ...more) {
            var { context, action, requests } = this;
            var chain = context[action](url);

            requests.push(chain);
            chain.then( (data) => this.index += 1 );

            if (more.length) return chain.then( () => this.execute(...more) );
            return chain;
        }

    };
})();

var sequence = new SequentialPromise(http, 'get');
var urls = [
    'url/name/space/0',
    'url/name/space/1',
    'url/name/space/2',
    'url/name/space/3',
    'url/name/space/4',
    'url/name/space/5',
    'url/name/space/6',
    'url/name/space/7',
    'url/name/space/8',
    'url/name/space/9'
];
var chain = sequence.execute(...urls);
var promises = sequence.requests;

chain.catch( () => console.warn(`EXECUTION STOPPED at ${sequence.index} for ${urls[sequence.index]}`) );

// console.log('>', chain, promises);

要旨

SequentialPromise


0

いずれかの時点で戻るPromise.reject('something')場合は、promiseのcatchブロックに入れられます。

promiseOne
  .then((result) => {
    if (!result) {
      return Promise.reject('No result');
    }
    return;
  })
  .catch((err) => {
    console.log(err);
  });

最初のプロミスが結果を返さない場合、コンソールには「結果なし」しか表示されません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.