자바 스크립트 약속-거부와 던지기


384

이 주제에 대해 여러 기사를 읽었지만 Promise.reject오류와 던지기 사이에 차이가 있는지는 분명하지 않습니다 . 예를 들어

Promise.reject 사용

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            return Promise.reject(new PermissionDenied());
        }
    });

던지기 사용하기

return asyncIsPermitted()
    .then(function(result) {
        if (result === true) {
            return true;
        }
        else {
            throw new PermissionDenied();
        }
    });

내가 선호 throw하는 것은 더 짧기 때문에 단순히 사용하는 것이지만 다른 것보다 이점이 있는지 궁금합니다.


9
두 방법 모두 정확히 동일한 응답을 생성합니다. .then()핸들러는 던져진 예외를 catch하고 자동으로 거부 약속으로 변합니다. throw 된 예외가 실행하는 것이 특히 빠르지 않다는 것을 읽었으므로 거부 된 약속을 반환하는 것이 약간 더 빠를 수도 있지만 여러 중요 브라우저에서 테스트를 고안해야한다고 생각합니다. 나는 throw가독성을 좋아 하기 때문에 개인적으로 사용 합니다.
jfriend00

@webduvet은 약속이 아닙니다-던지기와 함께 작동하도록 설계되었습니다.
joews

15
한 가지 단점 throw은 setTimeout과 같은 비동기 콜백 내에서 던져지면 약속이 거부되지 않는다는 것입니다. jsfiddle.net/m07van33 @Blondie 귀하의 답변이 정확했습니다.
Kevin B

@ joews 그것은 그것이 좋은 것을 의미하지는 않습니다;)
webduvet

1
아 맞아 따라서 내 의견에 대한 설명 " 약속되지 않은 비동기 콜백 내에서 발생한 경우 " 입니다. 나는 그것에 예외가 있다는 것을 알았습니다. 나는 그것이 무엇인지 기억하지 못했습니다. 나도 읽기가 더 쉽다는 것을 발견하고 reject매개 변수 목록에서 생략 할 수 있기 때문에 throw를 사용하는 것을 선호 합니다.
Kevin B

답변:


344

하나를 사용하는 것의 장점 throw은 없지만 작동하지 않는 특정한 경우 가 있습니다. 그러나 이러한 경우는 고칠 수 있습니다.

약속 콜백 내부에 있으면 언제든지을 사용할 수 있습니다 throw. 그러나 다른 비동기 콜백 인 경우을 사용해야합니다 reject.

예를 들어, 이것은 catch를 트리거하지 않습니다.

new Promise(function() {
  setTimeout(function() {
    throw 'or nah';
    // return Promise.reject('or nah'); also won't work
  }, 1000);
}).catch(function(e) {
  console.log(e); // doesn't happen
});

대신에 해결되지 않은 약속과 잡히지 않은 예외가 남게됩니다. 대신을 사용하려는 경우 reject입니다. 그러나 두 가지 방법으로이 문제를 해결할 수 있습니다.

  1. 제한 시간 내에 원래 약속의 거부 기능을 사용하여 :

new Promise(function(resolve, reject) {
  setTimeout(function() {
    reject('or nah');
  }, 1000);
}).catch(function(e) {
  console.log(e); // works!
});

  1. 타임 아웃을 약속함으로써 :

function timeout(duration) { // Thanks joews
  return new Promise(function(resolve) {
    setTimeout(resolve, duration);
  });
}

timeout(1000).then(function() {
  throw 'worky!';
  // return Promise.reject('worky'); also works
}).catch(function(e) {
  console.log(e); // 'worky!'
});


54
사용할 수없는 약속되지 않은 비동기 콜백 내부의 장소를 언급 할 가치가 있습니다 throw error.이 또한 return Promise.reject(err)OP가 비교하도록 요청한 것입니다. 이것이 기본적으로 약속 안에 비동기 콜백을 넣지 않아야하는 이유입니다. 비동기적인 모든 것을 약속하고 이러한 제한이 없습니다.
jfriend00

9
"그러나 다른 종류의 콜백 인 경우"는 "그러나 다른 종류의 비동기 콜백 인 경우"입니다. 콜백은 동기적일 수 있습니다 (예 :와 함께 Array#forEach).
Félix Saparelli

2
@KevinB는이 줄을 읽습니다. "throw가 작동하지 않는 특정한 경우가 있습니다." 그리고 "약속 콜백 내부에있을 때마다 throw를 사용할 수 있습니다. 그러나 다른 비동기 콜백에있는 경우 거부를 사용해야합니다." 예제 스 니펫이 throw작동하지 않고 대신 Promise.reject더 나은 선택 인 사례를 보여줄 것 입니다. 그러나 스 니펫은이 두 가지 선택에 영향을받지 않으며 선택한 항목에 관계없이 동일한 결과를 제공합니다. 뭔가 빠졌습니까?
Anshul

2
예. setTimeout에서 throw를 사용하면 catch가 호출되지 않습니다. 콜백 reject에 전달 된 것을 사용해야합니다 new Promise(fn).
Kevin B

2
@KevinB 함께있어 주셔서 감사합니다. OP가 제시 한 예는 그가 구체적으로 비교 return Promise.reject()하고 싶다고 언급했다 throw. 그는 구문 reject에서 주어진 콜백에 대해서는 언급하지 않았습니다 new Promise(function(resolve, reject)). 따라서 두 스 니펫이 리콜 콜백을 사용해야 할 시점을 올바르게 보여 주지만 OP의 질문은 그렇지 않았습니다.
Anshul

200

또 다른 중요한 사실이다 reject() 않습 유사한 제어 흐름을 종료 return문을 수행합니다. 반대로 throw제어 흐름은 종료됩니다.

예:

new Promise((resolve, reject) => {
  throw "err";
  console.log("NEVER REACHED");
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));

vs

new Promise((resolve, reject) => {
  reject(); // resolve() behaves similarly
  console.log("ALWAYS REACHED"); // "REJECTED" will print AFTER this
})
.then(() => console.log("RESOLVED"))
.catch(() => console.log("REJECTED"));


50
글쎄 요점은 맞지만 비교는 까다 롭습니다. 일반적으로을 작성하여 거부 된 약속을 반환해야 return reject()하므로 다음 줄은 실행되지 않습니다.
AZ.

7
왜 반품을 원하십니까?
lukyer

31
이 경우 return reject()간단히 말하면 reject(); return흐름을 종료하는 것입니다. 실행기 의 반환 값 (전달 된 함수 new Promise)은 사용되지 않으므로 안전합니다.
Félix Saparelli

47

예, 가장 큰 차이점은 거부 는 약속이 거부 된 후에 수행되는 콜백 함수이며 throw 는 비동기 적으로 사용할 수 없다는 것입니다. 거부를 사용하도록 선택한 경우 코드는 비동기 방식으로 계속 정상적으로 실행되지만 throw 는 리졸버 기능 완료를 우선 순위로 둡니다 (이 기능은 즉시 실행 됨).

내가 본 문제를 분명히하는 데 도움이되는 예는 다음과 같이 거부로 Timeout 함수를 설정할 수 있다는 것입니다.

new Promise(_, reject) {
 setTimeout(reject, 3000);
});

위는 던지기로 쓸 수 없었습니다.

작은 예제에서 구별 할 수없는 차이가 있지만보다 복잡한 비동기 개념을 처리 할 때는 둘 사이의 차이가 극심 할 수 있습니다.


1
이것은 핵심 개념처럼 들리지만 서면으로 이해하지 못합니다. 여전히 약속에 너무 새로운 것 같아요.
David Spector

43

TLDR : 함수는 때때로 약속을 반환하고 때로는 예외를 던질 때 사용하기가 어렵습니다. 비동기 함수를 작성할 때 거부 된 약속을 반환하여 신호 실패를 선호합니다.

귀하의 특정 예는 그들 사이의 중요한 차이점을 모호하게합니다 :

약속 체인 에서 오류를 처리하기 때문에 throw 된 예외가 자동으로 변환됩니다. 거부 된 약속 됩니다. 이것은 왜 그들이 상호 교환 가능한 것처럼 보일 수 있는지 설명 할 수 있습니다.

아래 상황을 고려하십시오.

checkCredentials = () => {
    let idToken = localStorage.getItem('some token');
    if ( idToken ) {
      return fetch(`https://someValidateEndpoint`, {
        headers: {
          Authorization: `Bearer ${idToken}`
        }
      })
    } else {
      throw new Error('No Token Found In Local Storage')
    }
  }

비동기 및 동기화 오류 사례를 모두 지원해야하기 때문에 이것은 안티 패턴입니다. 다음과 같이 보일 수 있습니다.

try {
  function onFulfilled() { ... do the rest of your logic }
  function onRejected() { // handle async failure - like network timeout }
  checkCredentials(x).then(onFulfilled, onRejected);
} catch (e) {
  // Error('No Token Found In Local Storage')
  // handle synchronous failure
} 

좋지 않으며 여기에 정확히 어디에 Promise.reject (전 세계적으로 이용 가능한) 구출의 와 효과적으로 차별화됩니다 throw. 리 팩터는 이제 다음과 같습니다.

checkCredentials = () => {
  let idToken = localStorage.getItem('some_token');
  if (!idToken) {
    return Promise.reject('No Token Found In Local Storage')
  }
  return fetch(`https://someValidateEndpoint`, {
    headers: {
      Authorization: `Bearer ${idToken}`
    }
  })
}

이제 catch()네트워크 장애 토큰 부족에 대한 동기 오류 검사에 하나만 사용할 수 있습니다 .

checkCredentials()
      .catch((error) => if ( error == 'No Token' ) {
      // do no token modal
      } else if ( error === 400 ) {
      // do not authorized modal. etc.
      }

1
그러나 Op의 예제는 항상 약속을 반환합니다. 이 질문은 당신이 사용해야 할 것인지 Promise.reject또는 throw거부 된 약속 (다음 약속으로 넘어갈 약속)을 ​​돌려주고 싶을 때를 언급합니다 .catch().
Marcos Pereira

@maxwell-나는 당신을 좋아합니다. 동시에 가져 오기에서 catch를 추가하고 예외를 throw하면 try ... catch를 사용하는 것이 안전합니다 ... 예외 흐름에 대한 완벽한 세계는 없지만 하나를 사용한다고 생각합니다. 단일 패턴은 의미가 있으며 패턴 조합은 안전하지 않습니다 (패턴과 반 패턴 유추와 일치).
user3053247

1
훌륭한 답변이지만 여기에 결함이 있습니다.이 패턴은 Promise.reject를 반환하여 모든 오류를 처리한다고 가정합니다 .checkCredentials ()에서 단순히 발생할 수있는 모든 예기치 않은 오류는 어떻게됩니까?
chenop

1
예, 당신은 맞습니다 @chenop-예기치 않은 오류를 잡으려면 try / catch를 계속 감싸 야합니다.
maxwell

@maxwell의 경우를 이해하지 못합니다. 당신은 당신이 그래서를 구성 할 수 없습니다 checkCredentials(x).then(onFulfilled).catch(e) {}, 그리고이 catch핸들을 모두 거부하는 경우와 발생 오류 사례를?
벤 휠러

5

시험해 볼만한 예. throw 대신 거부를 사용하려면 isVersionThrow를 false로 변경하십시오.

const isVersionThrow = true

class TestClass {
  async testFunction () {
    if (isVersionThrow) {
      console.log('Throw version')
      throw new Error('Fail!')
    } else {
      console.log('Reject version')
      return new Promise((resolve, reject) => {
        reject(new Error('Fail!'))
      })
    }
  }
}

const test = async () => {
  const test = new TestClass()
  try {
    var response = await test.testFunction()
    return response 
  } catch (error) {
    console.log('ERROR RETURNED')
    throw error 
  }  
}

test()
.then(result => {
  console.log('result: ' + result)
})
.catch(error => {
  console.log('error: ' + error)
})

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