Promise.catch 핸들러 안에 넣을 수없는 이유는 무엇입니까?


127

Errorcatch 콜백 내부를 던져서 프로세스가 다른 범위에있는 것처럼 오류를 처리하도록 할 수 없습니까?

내가 console.log(err)아무 것도 인쇄 하지 않으면 어떤 일이 일어 났는지 전혀 모른다. 과정은 막 끝납니다 ...

예:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

메인 스레드에서 콜백이 실행되면 왜 Error블랙홀에 의해 삼키는가?


11
블랙홀로 삼키지 않습니다. .catch(…)돌아 오는 약속을 거부 합니다.
Bergi


대신에 .catch((e) => { throw new Error() }), 쓰거나 .catch((e) => { return Promise.reject(new Error()) })간단히.catch((e) => Promise.reject(new Error()))
chharvey

1
@chharvey 귀하의 의견에있는 모든 코드 스 니펫은 초기 동작이 가장 명확하다는 것을 제외하고는 정확히 동일한 동작을합니다.
Сергей Гринько

답변:


157

다른 사람들이 설명했듯이, "블랙홀"은 .catch약속을 거부하고 체인을 계속 던지고 더 이상 캐치가 없어서 끝나지 않은 체인으로 연결되어 오류를 삼킨다 (나쁜!).

무슨 일이 일어나고 있는지 확인하기 위해 한 번 더 캐치를 추가하십시오.

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

체인 중간에 걸쇠는 실패한 단계에도 불구하고 체인이 진행되도록하려는 경우에 유용하지만 정보 다시 기록 또는 정리 단계와 같은 작업을 수행 한 후에도 오류계속 발생 하는 경우 다시 던지는 것이 유용합니다. 던졌습니다.

장난

원래 의도했던대로 웹 콘솔에서 오류를 오류로 표시하려면이 트릭을 사용하십시오.

.catch(function(err) { setTimeout(function() { throw err; }); });

줄 번호조차도 남아 있으므로 웹 콘솔의 링크를 사용하면 (원래) 오류가 발생한 파일과 줄로 바로 이동합니다.

작동하는 이유

약속 이행 또는 거부 처리기라고하는 함수의 예외는 자동으로 반환 할 약속의 거부로 변환됩니다. 함수를 호출하는 약속 코드가이를 처리합니다.

반면에 setTimeout에 의해 호출 된 함수는 항상 JavaScript 안정적인 상태에서 실행됩니다. 즉, JavaScript 이벤트 루프에서 새 주기로 실행됩니다. 예외는 아무것도 잡지 않고 웹 콘솔로 가져옵니다. 때문에 err원래 스택, 파일과 줄 번호를 포함하여 오류에 대한 모든 정보를 보유하고, 여전히 올바르게보고됩니다.


3
Jib, 이건 흥미로운 속임수입니다. 왜 그것이 효과가 있는지 이해하도록 도와 드릴까요?
Brian Keith

8
그 트릭에 대해 : 당신은 당신이 기록하고 싶어서 던지고 있습니다. 왜 직접 기록하지 않습니까? 이 트릭은 '무작위'시간에 잡을 수없는 오류를 던질 것입니다. 그러나 예외 (그리고 약속을 처리하는 방식)의 모든 아이디어 는 오류를 잡아서 처리 하는 호출자의 책임 을 만드는 것입니다. 이 코드는 호출자가 오류를 처리하는 것을 효과적으로 불가능하게합니다. 왜 당신을 위해 그것을 처리하는 기능을하지 않습니까? function logErrors(e){console.error(e)}그런 다음처럼 사용하십시오 do1().then(do2).catch(logErrors). 답변 자체는 훌륭한 btw, +1
Stijn de Witt

3
@jib 나는이 경우와 같이 다소간 연결된 많은 약속을 포함하는 AWS 람다를 작성하고 있습니다. 오류 발생시 AWS 경보 및 알림을 활용하려면 람다 충돌로 오류를 발생시켜야합니다 (추측). 트릭이 이것을 얻는 유일한 방법입니까?
masciugo

2
@StijndeWitt 내 경우에는 window.onerror이벤트 처리기 에서 서버에 오류 세부 정보를 보내려고했습니다 . 만 수행하여 setTimeout트릭이 수행 할 수 있습니다. 그렇지 않으면 window.onerrorPromise에서 발생한 오류에 대한 정보를 듣지 못합니다.
hudidit

1
@hudidit 아직도, console.log또는 postErrorToServer,해야 할 일을 할 수 있습니다. 어떤 코드에 window.onerror있 든지 별도의 함수로 분해 할 수없고 2 곳에서 호출 할 수있는 이유가 없습니다 . 아마도 setTimeout라인 보다 짧을 것입니다 .
Stijn de Witt

46

여기서 이해해야 할 중요한 사항

  1. thencatch함수 는 모두 새로운 promise 객체를 반환합니다.

  2. 던지거나 명시 적으로 거부하면 현재 약속이 거부 된 상태로 이동합니다.

  3. 이후 thencatch새로운 약속 개체를 반환, 그들은 체인 될 수있다.

  4. 약속 핸들러 ( then또는 catch) 내에서 던지거나 거부 하면 다음 거부 핸들러에서 체인 경로 아래로 처리됩니다.

  5. jfriend00에서 언급했듯이 thencatch핸들러는 동 기적으로 실행되지 않습니다. 핸들러가 발생하면 즉시 종료됩니다. 따라서 스택이 풀리고 예외가 손실됩니다. 그렇기 때문에 예외를 던지면 현재의 약속이 거부됩니다.


귀하의 경우 do1에는 Error물건 을 던져서 내부 를 거부 합니다. 이제 현재 약속은 거부 된 상태가되고 컨트롤은 다음 처리기 (이 then경우에는 처리자)로 전송됩니다 .

이후 then핸들러가 거부 핸들러가없는의는 do2전혀 실행되지 않습니다. console.log내부 를 사용하여 이를 확인할 수 있습니다 . 현재 약속에는 거부 핸들러가 없으므로 이전 약속의 거부 값으로 거부되며 제어는 다음 핸들러 ()로 전송됩니다 catch.

catch거부 처리기와 마찬가지로 console.log(err.stack);내부 에서 처리 할 때 오류 스택 추적을 볼 수 있습니다. 이제 Error객체에서 객체를 던지 므로 반환 된 약속 catch도 거부 된 상태가됩니다.

에 거부 핸들러를 첨부하지 않았으므로 거부 catch를 관찰 할 수 없습니다.


체인을 분할하고 다음과 같이 더 잘 이해할 수 있습니다

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

얻을 수있는 출력은 다음과 같습니다.

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

catch핸들러 1 내에서 promise거부 된 오브젝트 값을 얻습니다 .

동일한 방법으로 catch핸들러 1이 리턴 한 약속 도 거부 된 것과 동일한 오류 promise로 거부되며 두 번째 catch핸들러 에서이를 관찰 합니다.


3
또한 추가 가치가있을 수도 .then(), 그렇지 않으면 그들을 잡을 수있는 예외 핸들러가 없을 것, 그 안에서 예외 거부로 전환 할 필요가 있으므로 핸들러 (그들은 실행하기 전에 스택이 풀어진입니다) 비동기입니다.
jfriend00

7

setTimeout()위에서 설명한 방법을 시도했습니다 ...

.catch(function(err) { setTimeout(function() { throw err; }); });

짜증나게, 나는 이것이 완전히 테스트 할 수 없다는 것을 알았습니다. 비동기 오류가 발생하므로 시간 오류가 발생하면 청취가 중지 try/catch되므로 명령문 내부에 래핑 할 수 없습니다 catch.

완벽하게 작동하는 리스너를 사용하는 것으로 되돌아갔습니다. JavaScript가 사용되는 방식이기 때문에 테스트가 가능했습니다.

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});

3
Jest에는 이 상황을 처리해야하는 타이머 모형 이 있습니다.
jordanbtucker

2

에 따르면 사양을 (3.III.d 참조) :

디. 호출하면 예외가 발생하면 e
  . resolvePromise 또는 rejectPromise가 호출 된 경우 무시하십시오.
  비. 그렇지 않으면 이유가 e 인 약속을 거부하십시오.

then, 기능에 예외가 발생하면 예외 가 포착되고 약속이 거부됩니다. catch여기 이해가되지 않습니다. 바로 가기입니다..then(null, function() {})

코드에 처리되지 않은 거부를 기록하려고합니다. 대부분의 도서관은이를 unhandledRejection위해 노력 합니다. 여기 에 대한 토론과 관련된 요점 이 있습니다.


그것은 것을 언급 할만큼 가치의 unhandledRejection후크가 클라이언트 측에서 서버 측 자바 스크립트입니다 다른 브라우저는 서로 다른 솔루션을 가지고있다. 아직 표준화하지 않았지만 느리지 만 확실하게 진행되고 있습니다.
Benjamin Gruenbaum 2016 년

1

나는 이것이 조금 늦었다는 것을 알고 있지만, 나는이 스레드를 보았고 어떤 솔루션도 나를 위해 구현하기 쉽지 않았기 때문에 내 자신을 생각해 냈다.

약속을 반환하는 작은 도우미 함수를 다음과 같이 추가했습니다.

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

그런 다음 약속 체인에 오류를 발생시키고 약속을 거부하려는 특정 위치가있는 경우 위의 함수에서 구성된 오류와 함께 간단히 반환합니다.

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

이렇게하면 약속 체인 내부에서 추가 오류를 처리 할 수 ​​있습니다. '정상적인'약속 오류를 처리하려면 '자가 던진'오류를 개별적으로 처리하도록 catch를 확장하십시오.

이것이 도움이되기를 바랍니다. 첫 번째 stackoverflow 답변입니다!


Promise.reject(error)대신 new Promise(function (resolve, reject){ reject(error) })(어쨌든 반환 진술이 필요합니다)
Funkodebat

0

예, 삼키는 오류를 약속 .catch하며 다른 답변에서 자세히 설명하는 것처럼 오류 만 잡을 수 있습니다 . Node.js에 있고 정상적인 throw동작 을 재현하고 스택 추적을 콘솔에 인쇄하고 프로세스를 종료하려면 다음을 수행하십시오.

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});

1
아닙니다, 그것은 당신이 가진 모든 약속 체인 의 끝에 그것을 넣어야하기 때문에 충분하지 않습니다 . 이벤트 unhandledRejection
Bergi

네, 약속을 묶어서 출구가 마지막 기능이며 나중에 잡히지 않는다고 가정합니다. 언급 한 이벤트는 블루 버드를 사용하는 경우에만 있다고 생각합니다.
Jesús Carrera

Bluebird, Q, 기본 약속,… 표준이 될 것입니다.
Bergi
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.