약속 체인을 끊고 체인이 끊어진 (거부 된) 단계에 따라 함수를 호출하십시오.


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 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
이것이 더 복잡해지면 도움이 될 비동기 자바 스크립트 라이브러리가있다
lucuma

Promise.prototype.catch()MDN의 예는 똑같은 문제에 대한 솔루션을 보여줍니다.
toraritte

답변:


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핸들러 (두 번째 인수 then) 본질적으로 (a 같은 에러 정정 메커니즘 catch블록). 에 오류가 발생 handleErrorOne하면 다음 catch 블록 (catch(e2) ) 등에서 합니다.

이것은 분명히 당신이 의도 한 것이 아닙니다.

무슨 일이 있어도 전체 해결 체인이 실패하기를 원한다고 가정 해 봅시다.

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

참고 : 거부 handleErrorOne된 경우에만 호출되기 때문에 현재 위치 를 떠날 수 있습니다 stepOne(체인의 첫 번째 기능 이므로이 시점에서 체인이 거부되면 해당 기능의 약속 때문일 수 있음을 알고 있습니다) .

중요한 변화는 다른 함수에 대한 오류 처리기가 주요 약속 체인의 일부가 아니라는 것입니다. 대신, 각 단계에는 자체 "서브 체인"이 있으며onRejected 단계가 거부 된 경우에만 호출되는 있습니다 (하지만 주 체인에 직접 도달 할 수는 없음).

이 작품 이유는 둘이다 onFulfilled하고 onRejected받는 선택적 인수있는 then방법은. 약속이 이행되고 (즉, 해결됨) then체인 의 다음 약속 에 onFulfilled처리기 가없는 경우 해당 처리기가있는 체인이있을 때까지 체인이 계속됩니다.

이는 다음 두 줄이 동일 함을 의미합니다.

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

그러나 다음 줄은 위의 두 줄과 동일 하지 않습니다 .

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

Angular의 약속 라이브러리 $q는 kriskowal의 Q라이브러리 (더 풍부한 API를 가지고 있지만에서 찾을 수있는 모든 것을 포함 $q)를 기반으로합니다. GitHub 의 Q API 문서 가 유용 할 수 있습니다. Q는 Promises / A + spec을 구현 합니다.then 은 약속 해결 동작이 정확히 작동 합니다.

편집하다:

또한 오류 처리기에서 체인을 벗어나려면 거부 된 약속을 반환하거나 오류 (거부 된 약속에 자동으로 잡히고 포장됩니다)를 throw해야합니다. 약속을 반환하지 않으면 반환 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

거부는 현재 기능을 종료합니까? 예를 들어 거부가 1 번 호출되면 resolve가 호출되지 않습니다.`if (bad) {reject (status); } resolve (results);`
SuperUberDuper

stepOne().then(stepTwo, handleErrorOne) `stepOne (). then (null, handleErrorOne) .then (stepTwo)`이것들은 완전히 동일합니까? stepOne두 번째 코드 줄 에서 거부하는 경우 실행 stepTwo되지만 첫 번째 코드 만 실행 handleErrorOne되고 중지됩니다. 아니면 뭔가 빠졌습니까?
JeFf

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.') })
Craig van Tonder

1
@CraigvanTonder 당신은 약속 내에서 던질 수 있으며 그것은 당신의 코드와 동일하게 작동합니다 :.then(user => { if (user) throw 'The email address already exists.' })
Francisco Presencia

1
이것이 유일한 정답입니다. 그렇지 않으면 1 단계에 오류가 있어도 3 단계는 계속 실행됩니다.
wdetac

1
명확히하기 위해 stepOne ()에서 오류가 발생하면 두 chainError가 올바르게 호출됩니까? 이것이 바람직하다면. 나는 이것을 이해하는 스 니펫을 가지고 있는데, 내가 잘못 이해했는지 확실하지 않습니다 .- runkit.com/embed/9q2q3rjxdar9
user320550

10

필요한 것은 .then()특별한 케이스를 시작하고 특별한 케이스를 마무리 하는 반복 체인입니다.

요점은 실패 사례의 단계 번호를 최종 오류 처리기로 리플하는 것입니다.

  • 시작 : step(1)무조건 호출 합니다.
  • 반복 패턴 : .then()다음 콜백으로 체인 a 를 연결하십시오.
    • 성공 : 호출 단계 (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, 이것은 질문 된 질문에 대한 답변입니다. "오류 체인의 모든 함수를 호출하지 않고 거부시 함수를 호출 할 수 있도록 약속을 작성하는 방법은 무엇입니까?" 그리고 질문의 제목, "Break promise chain and break the chain in step in the chain in the broken (rejected)"
Beetroot-Beetroot

내가 말했듯이, 그것은 유익하고 내 게시물 에이 솔루션을 포함 시켰습니다 (더 자세하게). 이 접근 방식은 체인을 계속 사용할 수 있도록 사물을 수정하기위한 것입니다. 내가 찾고있는 것을 성취 할 수는 있지만 받아 들인 대답의 접근법만큼 자연스럽지 않습니다. 즉, 제목과 질문으로 표현 된 것을하고 싶다면 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()첫 번째 단계의 실패 사례 로 돌아 가면 해당 약속을 거부하여 2에서 errorCallback이 호출됩니다 then(...).


세상에서 뭐야 ... 그게 내가 한 짓이야! 내 게시물에서 내가 시도한 것을 참조하십시오. 그러나 체인이 시작되어 실행 step(2)됩니다. 이제는 다시 시도했지만 일어나지 않았습니다. 난 너무 혼란 스러워요.
m59

1
나는 당신이 그것을 언급 한 것을 보았습니다. 그래도 이상하다. 포함 된 함수는 성공적으로 해결 return step(2);될 때만 호출해야 step(1)합니다.
Zajn

그것을 긁으십시오-확실히 일어나고 있습니다. 내 게시물에서 말했듯이을 사용하지 않으면 return $q.reject()체인이 계속 작동합니다. 이 경우 return response엉망이되었습니다. 다음을 참조하십시오 : jsbin.com/EpaZIsIp/6/edit
m59

흠. 변경했을 때 게시 한 jsbin에서 작동하는 것처럼 보이지만 뭔가를 놓쳤습니다.
Zajn

그래, 나는 그것이 지금 작동하지 않는 것을 분명히 본다. 나를 위해 드로잉 보드로 돌아갑니다!
Zajn

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를 사용하십시오.

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

MDN 에서 찾은 Promise.prototype.catch() 매우 도움이 아래.

(허용 된 답변 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 Await를 사용하도록 약속 체인에 리팩터링하는 것입니다. 그런 다음 함수에서 돌아와 나머지 동작을 건너 뛸 수 있습니다.

나는이 패턴에 대해 1 년 넘게 머리를 쳤다.


순수 IE를 사용하는 경우 async / await는 지원되지 않습니다.
ndee

0

SequentialPromise 모듈 사용

의향

각 작업의 현재 색인을 순서대로 추적하면서 요청을 순차적으로 실행하는 책임을 가진 모듈을 제공하십시오. 명령 패턴 에서 작업 정의유연성을 위해 .

참가자

  • 문맥 : 멤버 메소드가 조작을 수행하는 오브젝트입니다.
  • SequentialPromise : execute각 작업을 체인 및 추적 하는 방법을 정의합니다 . SequentialPromise는 수행 된 모든 작업에서 Promise-Chain을 반환합니다.
  • Invoker : SequentialPromise 인스턴스를 만들어 컨텍스트 및 액션 execute을 제공하고 각 작업에 대한 서수 옵션 목록을 전달하면서 메소드를 호출 합니다.

결과

Promise Resolution의 순 서적 동작이 필요한 경우 SequentialPromise를 사용하십시오. SequentialPromise는 약속이 거부 된 인덱스를 추적합니다.

이행

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);

요점

순차적 약속


0

어느 시점에서 당신이 돌아 오면 Promise.reject('something')당신은 약속의 캐치 블록에 던져 질 것입니다.

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.