프라 미스 체인에서 여러 캐치 처리


125

나는 아직 약속에 상당히 익숙하지 않고 현재 블루 버드를 사용하고 있지만 어떻게 최선을 다해야할지 잘 모르겠습니다.

예를 들어 다음과 같은 익스프레스 앱 내에 약속 체인이 있습니다.

repository.Query(getAccountByIdQuery)
        .catch(function(error){
            res.status(404).send({ error: "No account found with this Id" });
        })
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .catch(function(error) {
            res.status(406).send({ OldPassword: error });
        })
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error){
            console.log(error);
            res.status(500).send({ error: "Unable to change password" });
        });

그래서 내가 추구하는 행동은 다음과 같습니다.

  • ID로 계정을 가져갑니다.
  • 이 시점에서 거부가 있으면 폭탄을 터 뜨리고 오류를 반환합니다.
  • 오류가 없으면 반환 된 문서를 모델로 변환합니다.
  • 데이터베이스 문서로 비밀번호 확인
  • 비밀번호가 일치하지 않으면 폭탄을 터 뜨리고 다른 오류를 반환합니다.
  • 오류가 없으면 암호를 변경하십시오
  • 그런 다음 성공 반환
  • 다른 문제가 있으면 500을 반환하십시오.

그래서 현재 캐치가 체인을 멈추지 않는 것 같고 말이됩니다. 그래서 어떻게 든 오류에 따라 특정 지점에서 체인을 멈추게하는 방법이 있는지, 아니면 더 좋은 방법이 있는지 궁금합니다. 의 경우와 같이 분기 동작의 형태를 얻기 위해 이것을 구조화합니다 if X do Y else Z.

어떤 도움이라도 좋을 것입니다.


다시 던지거나 일찍 돌아올 수 있습니까?
Pieter21 2014 년

답변:


126

이 동작은 동기 발생과 똑같습니다.

try{
    throw new Error();
} catch(e){
    // handle
} 
// this code will run, since you recovered from the error!

그것은 .catch오류로부터 복구 할 수 있다는 점의 절반입니다 . 상태가 여전히 오류임을 알리기 위해 다시 던지는 것이 바람직 할 수 있습니다.

try{
    throw new Error();
} catch(e){
    // handle
    throw e; // or a wrapper over e so we know it wasn't handled
} 
// this code will not run

그러나 나중에 처리기가 오류를 포착하기 때문에 이것만으로는 귀하의 경우에 작동하지 않습니다. 여기서 진짜 문제는 일반화 된 "아무것도 처리"오류 처리기는 일반적으로 나쁜 관행이며 다른 프로그래밍 언어와 생태계에서 극도로 눈살을 찌푸린다는 것입니다. 이러한 이유로 Bluebird는 형식화 및 술어 catch를 제공합니다.

추가 된 이점은 비즈니스 로직이 요청 / 응답주기를 전혀 인식하지 않아도된다는 것입니다. 클라이언트에 어떤 HTTP 상태와 오류가 발생하는지 결정하는 것은 쿼리의 책임이 아니며 나중에 앱이 커짐에 따라 클라이언트에 보내는 내용과 비즈니스 논리 (DB 쿼리 방법 및 데이터 처리 방법)를 분리 할 수 ​​있습니다. (어떤 http 상태 코드, 텍스트 및 응답).

다음은 코드를 작성하는 방법입니다.

먼저, 나는 Bluebird가 이미 제공하는 하위 클래스 .Query를 던질 NoSuchAccountErrorPromise.OperationalError입니다. 오류를 하위 분류하는 방법을 잘 모르겠 으면 알려주세요.

추가로 하위 클래스를 AuthenticationError만들고 다음과 같은 작업을 수행합니다.

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}

보시다시피-매우 깨끗하고 프로세스에서 일어나는 일에 대한 지침 매뉴얼처럼 텍스트를 읽을 수 있습니다. 요청 / 응답과도 분리됩니다.

이제 경로 처리기에서 다음과 같이 호출합니다.

 changePassword(params)
 .catch(NoSuchAccountError, function(e){
     res.status(404).send({ error: "No account found with this Id" });
 }).catch(AuthenticationError, function(e){
     res.status(406).send({ OldPassword: error });
 }).error(function(e){ // catches any remaining operational errors
     res.status(500).send({ error: "Unable to change password" });
 }).catch(function(e){
     res.status(500).send({ error: "Unknown internal server error" });
 });

이런 식으로 논리가 모두 한곳에 있고 클라이언트에 대한 오류를 처리하는 방법에 대한 결정이 모두 한곳에 있으며 서로 어수선하지 않습니다.


11
.catch(someSpecificError)특정 오류에 대한 중간 처리기 가있는 이유 는 특정 유형의 오류 (즉, 무해한)를 포착하고이를 처리하고 다음 흐름을 계속하려는 경우라고 추가 할 수 있습니다. 예를 들어, 일련의 작업이 포함 된 시작 코드가 있습니다. 첫 번째는 디스크에서 구성 파일을 읽는 것이지만 해당 구성 파일이 누락 된 경우 OK 오류 (프로그램에 기본적으로 내장되어 있음)이므로 특정 오류를 처리하고 나머지 흐름을 계속할 수 있습니다. 나중까지 떠나지 않는 것이 더 나은 정리가있을 수도 있습니다.
jfriend00 2014 년

1
"이것이 .catch 요점의 절반입니다-오류에서 복구 할 수있는 것"이라고 생각했지만 더 명확히 해주셔서 감사합니다. 좋은 예입니다.
Benjamin Gruenbaum 2014 년

1
블루 버드를 사용하지 않으면 어떻게됩니까? 일반 es6 약속에는 catch를 위해 전달되는 문자열 오류 메시지 만 있습니다.
clocksmith

3
ES6의 @clocksmith는 모든 것을 포착하고 instanceof수동으로 chceks를 수행하는 데 갇혀 있다고 약속합니다 .
Benjamin Gruenbaum

1
Error 객체의 서브 클래 싱에 대한 참조를 찾는 사람들은 bluebirdjs.com/docs/api/catch.html#filtered-catch를 읽어 보세요. 기사는 또한 여기에 주어진 다중 캐치 답변을 거의 재현합니다.
mummybot

47

.catchtry-catch문장 처럼 작동합니다 . 즉, 마지막에 하나의 캐치 만 필요합니다.

repository.Query(getAccountByIdQuery)
        .then(convertDocumentToModel)
        .then(verifyOldPassword)
        .then(changePassword)
        .then(function(){
            res.status(200).send();
        })
        .catch(function(error) {
            if (/*see if error is not found error*/) {
                res.status(404).send({ error: "No account found with this Id" });
            } else if (/*see if error is verification error*/) {
                res.status(406).send({ OldPassword: error });
            } else {
                console.log(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        });

1
그래 나는 이것을 알고 있었지만 거대한 에러 체인을하고 싶지 않았고, 필요할 때 그것을하는 것이 더 읽기 쉬운 것처럼 보였다. 따라서 결국에는 모든 것을 잡을 수 있지만 의도에 대해 더 설명적인 유형 오류 아이디어가 마음에 듭니다.
Grofit

8
@Grofit for what it 's value-Typed catches in Bluebird Petka (Esailija)의 아이디어였습니다. JS의 많은 사람들이 개념을 잘 모르기 때문에 그가 당신을 혼동하고 싶지 않았다고 생각합니다.
Benjamin Gruenbaum 2014 년

17

오류를 기반으로 특정 지점에서 체인을 강제로 중지시키는 방법이 있는지 궁금합니다.

아니요. 끝까지 버블 링되는 예외를 throw하지 않는 한 체인을 실제로 "종료"할 수 없습니다. 이를 수행하는 방법 은 Benjamin Gruenbaum의 답변 을 참조하십시오 .

그의 패턴의 파생은 오류 유형을 구별하는 것이 아니라 단일 일반 처리기 에서 보낼 수있는 statusCodebody필드가있는 오류를 사용하는 것 .catch입니다. 응용 프로그램 구조에 따라 그의 솔루션이 더 깨끗할 수 있습니다.

또는 어떤 형태의 분기 동작을 얻기 위해 이것을 구조화하는 더 좋은 방법이 있다면

예, promise를 사용하여 분기를 수행 할 수 있습니다 . 그러나 이것은 중첩 된 if-else 또는 try-catch 문에서하는 것처럼 체인을 떠나 중첩으로 "돌아가는"것을 의미합니다.

repository.Query(getAccountByIdQuery)
.then(function(account) {
    return convertDocumentToModel(account)
    .then(verifyOldPassword)
    .then(function(verification) {
        return changePassword(verification)
        .then(function() {
            res.status(200).send();
        })
    }, function(verificationError) {
        res.status(406).send({ OldPassword: error });
    })
}, function(accountError){
    res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
    console.log(error);
    res.status(500).send({ error: "Unable to change password" });
});

5

나는 이런 식으로 해왔다.

당신은 결국 당신의 캐치를 남겨 둡니다. 체인 중간에 오류가 발생하면 오류가 발생합니다.

    repository.Query(getAccountByIdQuery)
    .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
    .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')        
    .then(changePassword)
    .then(function(){
        res.status(200).send();
    })
    .catch((error) => {
    if (error.name === 'no_account'){
        res.status(404).send({ error: "No account found with this Id" });

    } else  if (error.name === 'wrong_old_password'){
        res.status(406).send({ OldPassword: error });

    } else {
         res.status(500).send({ error: "Unable to change password" });

    }
});

다른 기능은 아마도 다음과 같을 것입니다.

function convertDocumentToModel(resultOfQuery) {
    if (!resultOfQuery){
        throw new Error('no_account');
    } else {
    return new Promise(function(resolve) {
        //do stuff then resolve
        resolve(model);
    }                       
}

4

아마도 파티에 조금 늦었을 것입니다. 그러나 다음 .catch과 같이 중첩이 가능합니다 .

Mozilla 개발자 네트워크-약속 사용

편집 : 일반적으로 요청 된 기능을 제공하기 때문에 제출했습니다. 그러나이 특별한 경우에는 그렇지 않습니다. 이미 다른 사람들이 자세히 설명했듯이 .catch오류를 복구해야하기 때문입니다. 당신은, 예를 들면, 클라이언트에 응답 보낼 수 없습니다 여러 .catch 때문에 콜백을 .catch더 명시 적으로 return 결의 와 함께 undefined이 경우, 진행을 유발 .then하여 체인이 정말 해결되지 않은 경우에도 트리거는 잠재적으로 다음과 같은 원인이 .catch트리거 보내기 클라이언트에 대한 또 다른 응답으로 오류가 발생하고 UnhandledPromiseRejection길을 잃을 가능성이 있습니다. 이 복잡한 문장이 당신에게 의미가 있기를 바랍니다.


1
@AntonMenshov 당신이 맞습니다. 나는 중첩와 함께 자신의 원하는 동작은 아직 할 수없는 이유를 설명하는 내 대답을 확장
denkquer

2

대신 .then().catch()...할 수 있습니다 .then(resolveFunc, rejectFunc). 이 약속 체인은 당신이 그 과정에서 일을 처리한다면 더 좋을 것입니다. 다시 작성하는 방법은 다음과 같습니다.

repository.Query(getAccountByIdQuery)
    .then(
        convertDocumentToModel,
        () => {
            res.status(404).send({ error: "No account found with this Id" });
            return Promise.reject(null)
        }
    )
    .then(
        verifyOldPassword,
        () => Promise.reject(null)
    )
    .then(
        changePassword,
        (error) => {
            if (error != null) {
                res.status(406).send({ OldPassword: error });
            }
            return Promise.Promise.reject(null);
        }
    )
    .then(
        _ => res.status(200).send(),
        error => {
            if (error != null) {
                console.error(error);
                res.status(500).send({ error: "Unable to change password" });
            }
        }
    );

참고 : (가) if (error != null)해킹의 비트가 가장 최근의 오류와 상호 작용하는 것입니다.


1

위의 Benjamin Gruenbaum의 대답 은 복잡한 논리 시퀀스에 대한 최상의 솔루션 이라고 생각 하지만 여기에 더 간단한 상황에 대한 대안이 있습니다. 다음 또는 문 을 건너 뛰기 위해 errorEncountered플래그와 함께 사용합니다 . 따라서 다음과 같이 보일 것입니다.return Promise.reject()thencatch

let errorEncountered = false;
someCall({
  /* do stuff */
})
.catch({
  /* handle error from someCall*/
  errorEncountered = true;
  return Promise.reject();
})
.then({
  /* do other stuff */
  /* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
  if (errorEncountered) {
    return;
  }
  /* handle error from preceding then, if it was executed */
  /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});

두 개 이상의 then / catch 쌍이있는 경우 Benjamin Gruenbaum의 솔루션을 사용해야합니다. 그러나 이것은 간단한 설정으로 작동합니다.

결승전 catch에는 return;이보다 더 많은 것이 있습니다 return Promise.reject();. 왜냐하면 then우리가 건너 뛸 후속 이 없기 때문이며 Node가 좋아하지 않는 처리되지 않은 Promise 거부로 간주됩니다. 위에 쓰여진 것처럼 결승전 catch은 평화롭게 해결 된 약속을 반환합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.