Programing

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

crosscheck 2020. 7. 7. 07:47
반응형

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


최신 정보:

이 게시물의 향후 시청자를 돕기 위해 나는 pluma 's answer 데모를 만들었습니다 .

질문:

나의 목표는 매우 간단 해 보인다.

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

코드가 예상대로 작동하지 않는 이유는 실제로 생각한 것과 다른 것을 수행하기 때문입니다.

다음과 같은 것이 있다고 가정 해 봅시다.

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을 구현합니다. Promises / A + 사양then 은 약속 해결 동작이 정확히 어떻게 작동 하는지에 대해 자세히 설명 합니다.

편집하다:

또한 오류 처리기에서 체인을 벗어나려면 거부 된 약속을 반환하거나 오류를 throw해야합니다 (거부 된 약속에 자동으로 잡히고 랩됩니다). 약속을 반환하지 않으면 반환 then값을 해결 약속으로 래핑합니다.

즉, 아무 것도 반환하지 않으면 가치에 대한 해결 된 약속을 효과적으로 반환하는 것 undefined입니다.


파티에 늦었지만이 간단한 솔루션이 나를 위해 일했습니다.

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

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

이를 통해 체인 에서 벗어날 수 있습니다 .


필요한 것은 .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.


거부 할 때 거부 오류를 전달 해야하는 경우 체인이 끝날 때까지 거부를 처리하거나 "다시 던져야"하는지 확인하는 함수에서 단계 오류 처리기를 래핑하십시오.

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

올바르게 이해하면 실패한 단계의 오류 만 표시하고 싶습니까?

첫 번째 약속의 실패 사례를 변경하는 것만 큼 간단합니다.

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

$q.reject()첫 번째 단계의 실패 사례 로 돌아 가면 해당 약속을 거부하여 errorCallback이 2nd에서 호출됩니다 then(...).


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


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 []
    }

})()

단계 실행에 오류 처리기를 별도의 체인 요소로 직접 첨부하십시오.

        // 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의 이름 지정을 사용하는 것과 동일한 패턴 입니다.


찾을 수 Promise.prototype.catch()MDN에 대한 예제 매우 도움이 아래.

(The accepted answer mentions then(null, onErrorHandler) which is basically the same as catch(onErrorHandler).)

Using and chaining the catch method

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

Gotchas when throwing errors

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

If it is resolved

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

The best solution is to refactor to your promise chain to use ES6 await's. Then you can just return from the function to skip the rest of the behavior.

I have been hitting my head against this pattern for over a year and using await's is heaven.


Try ro use this like 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);
});

참고URL : https://stackoverflow.com/questions/20714460/break-promise-chain-and-call-a-function-based-on-the-step-in-the-chain-where-it

반응형