비동기 / 대기 구문에서 거부하는 방법?


282

async / await 함수가 반환 한 약속을 어떻게 거부 할 수 있습니까?

예를 들어 원래

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

async / await로 번역

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

이 경우 어떻게이 약속을 올바르게 거절 할 수 있습니까?


20
Promise생성자 antipattern을 피하십시오 ! 첫 번째 스 니펫도 작성되었습니다foo(id: string): Promise<A> { return someAsyncPromise().then(()=>{ return 200; }, ()=>{ throw 400; }); }
Bergi

10
TypeScript와 관련이 없기 때문에이 질문의 코드를 바닐라 JS로 변환하는 것이 도움이 될 것이라고 생각합니다. 그렇게하면 편집 내용이 승인 될 수 있습니까?
Jacob Ford

답변:


328

가장 좋은 방법이다 포장 값와 거부 약속의 결과 포장 값 :throwErrorError

} catch (error) {
    throw new Error(400);
}

throw값만 가질 수 있지만 스택 추적 정보는 없습니다.

} catch (error) {
    throw 400;
}

또는 Error값을 줄 바꿈하여 거부 된 약속을 반환 하지만 관용적이지는 않습니다.

} catch (error) {
    return Promise.reject(new Error(400));
}

또는 단지 return Promise.reject(400);컨텍스트 정보가 없습니다.

(귀하 의 경우을 사용 TypeScript하고 foo의 retrn value가 Promise<A>이면을 사용합니다 return Promise.reject<A>(400 /*or error*/);)

에서 async/의 await상황, 마지막은 아마 의미가 일치하지 약간이지만, 그것은 작업을 수행합니다.

를 던지면 구문으로 결과를 Error소비하는 모든 것과 잘 어울 립니다.fooawait

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}

12
async / await는 비동기 흐름을 다시 동기화 구문으로 가져 오는 것에 관한 것이므로 IMO throw보다 낫습니다 Promise.reject(). throw 400다른 질문 인지 여부 입니다. OP에서 400을 거부하고 있으며 Error대신 거부해야한다고 주장 할 수 있습니다 .
unional

2
코드 체인 정말 비동기를 사용하는 경우 예, 그러나 / await를, 당신은 ..... 하드 여기에 입력, 내가 대답으로 시연하게됩니다
unional

1
catch 블록에서 주어진 오류가 아닌 새로운 오류를 던지려는 이유가 있습니까?
Adrian M

1
@sebastian-나는 당신이 무슨 뜻인지 모르겠습니다. 에서는 async기능에는 없다 resolve또는 reject기능. 있다 returnthrow해결에 관용적 인 방법을하며 거부하는 async함수의 약속을.
TJ Crowder

1
@ Jan-PhilipGehrcke-당신 은 할 수 는 있지만 결코 할 수 없습니다. 인스턴스를 생성하고 new명시 적으로 만듭니다. 또한 Error서브 클래스 ( class MyError extends Error) 가있는 경우에는 제외 할 수 없습니다 .
TJ Crowder

146

또한 catch()후드 아래에서 여전히 약속이 반환되기 때문에 비동기 작업을 호출 한 후에 단순히 함수 를 연결할 수 있다고 언급해야합니다 .

await foo().catch(error => console.log(error));

이런 식으로 try/catch구문이 마음에 들지 않으면 구문을 피할 수 있습니다 .


1
따라서 async함수 를 거부하려면 예외를 throw 한 다음 .catch()반환 Promise.reject하거나 호출 한 것처럼 예외를 잡습니다 reject. 나는 그것을 좋아한다!
icl7126

7
이것이 왜 받아 들여야하는지 이해하지 못합니다. 허용되는 답은 정리 될뿐만 아니라 await한 번의 모든 실패를 처리합니다 . 각각에 대해 매우 구체적인 사례가 필요 await하지 않으면 왜 이런 식으로 잡으려고하는지 모르겠습니다. 그냥 겸손한 의견.
edgaralienfoe

1
유스 케이스에 @jablesauce를 사용하면 각 await실패를 개별적 으로 포착해야 할 뿐만 아니라 오류에 대한 약속을 거부하는 Promise 기반 프레임 워크를 사용해야했습니다.
Reuven Karasik

그것은 나를 위해 작동하지 않았다. URL이 실패하면 catch 블록에 들어 가지 않는 것 같습니다. [응답] = 기다림 oauthGet ( ${host}/user/permissions/repositories_wrong_url/, accessToken, accessTokenSecret) .catch (err => {logger.error ( '저장소 권한을 가져올 수 없습니다', err); 콜백 (err);})
sn.anurag

1
await키워드 가 필요하지 않습니다 .
Ashish Rawat

12

약속을 받아들이고 오류가 없으면 데이터가 포함 된 배열을 반환하고 오류가 있으면 오류를 반환하는 래퍼 함수 를 만들 수 있습니다 .

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

ES7비동기 함수에서 다음 과 같이 사용하십시오 .

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}

1
사랑스러운 Go 구문을 사용하려는 시도이지만 우아함이별로없는 것 같습니다. 나는 그것을 사용하는 코드가 솔루션에서 가치를 빨아 들일 정도로 충분히 난독 화되는 것을 발견했다.
Kim

8

비동기 함수를 작성하는 더 좋은 방법은 시작에서 보류중인 약속을 반환 한 다음 거부에 대한 약속을 뱉어내는 것이 아니라 약속의 콜백 내에서 거부와 해결을 모두 처리하는 것입니다. 예:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

그런 다음 반환 된 약속에 메소드를 연결하십시오.

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

출처-이 튜토리얼 :

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise


5
이 질문은 async / await 사용에 대해 구체적으로 물었습니다. 약속을 사용하지 않음
Mak

이 답변은 결정적인 정답이 아닙니다. 이것은 위에 주어진 다른 답변에 대한 지원 답변이었습니다. 나는 그것을 의견으로 내려 놓았지만 코드가 있다고 대답하면 대답 필드가 더 좋습니다.
OzzyTheGiant

설명해 주셔서 감사합니다. 비동기 기능을 만드는 방법을 보여주는 것이 확실히 도움이됩니다. await를 사용하기 위해 두 번째 코드 블록을 업데이트하면 훨씬 더 관련성이 있고 유용합니다. 건배
Mak

답변을 업데이트하여 업데이트했습니다. 내가 놓친 부분을 알려주세요
Mak

4

여러 try-catch 블록을 사용하지 않고 새로운 접근 방식에서 거부 를 올바르게 처리 할 것을 제안합니다 .

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

를 Where to.ts의 기능에서 수입해야한다 :

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

크레딧은 다음 링크 에서 Dima Grossman으로갑니다 .


1
나는이 구성을 거의 독점적으로 (훨씬 더 깨끗하게) 사용하고 npmjs.com/package/await-to-js 주변에 'to'모듈이 있습니다. 해체 된 과제 앞에 놓아 둔 별도의 선언이 필요하지 않습니다. let [err]=오류 만 확인하는 경우 에도 수행 할 수 있습니다.
DKebler

3

이것은 @TJ Crowder의 답변이 아닙니다. 단지 코멘트에 응답하는 코멘트 "그리고 실제로 예외가 거부로 변환 될 경우 실제로 그것이 오류인지 귀찮게할지 확실하지 않습니다. 에러 만 던지는 이유는 아마도 적용되지 않을 것입니다. "

코드에서 async/를 사용하는 경우 await다음 Error대신에 거부하는 것이 좋습니다 400.

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}

3

나는 이것이 오래된 질문이라는 것을 알고 있지만 스레드를 우연히 발견했으며 여기에 예외 처리를 사용하지 않는 자주 반복되는 조언을 어기 게하는 오류와 거부 사이에 충돌이있는 것 같습니다. 예상 사례를 처리합니다. 설명 : 비동기 메소드가 사용자 인증을 시도하고 인증에 실패한 경우 이는 거부 (예상 된 두 가지 경우 중 하나)이며 오류 (예 : 인증 API를 사용할 수없는 경우)가 아닙니다.

머리카락을 나누지 않았 음을 확인하기 위해이 코드를 사용하여 세 가지 접근 방식에 대한 성능 테스트를 실행했습니다.

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

거기에있는 것들 중 일부는 Javascript 인터프리터에 대한 나의 불확실성 때문에 포함되어 있습니다 (한 번에 하나의 토끼 구멍을 내려 가고 싶습니다). 예를 들어 조건부 블록이 최적화되지 않도록 doSomething함수를 포함 하고 반환 값을 할당했습니다 dummyValue.

내 결과는 다음과 같습니다

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

작은 최적화를 찾아내는 데 어려움을 겪을 가치가없는 경우가 많이 있지만 대규모 시스템에서는 이러한 점이 누적 차이를 크게 만들 수 있으며 이는 상당히 큰 비교입니다.

그래서 ... 나는 대답이 접근 방식이 비동기 함수 내에서 예측할 수없는 오류를 처리해야한다고 기대하는 경우에 적합하다고 생각하지만 거부가 단순히 "계획 B와 함께 가야한다는 것을 의미하는 경우 (또는 C 또는 D…) "사용자 지정 응답 개체 사용을 거부하는 것이 좋습니다.


2
또한 해당 함수에 대한 호출이 약속 범위와 달리 비동기 함수에서 발생 된 오류를 버블 링 한 후 버블 링 범위의 try / catch 블록 내에있는 경우 비동기 함수 내에서 예기치 않은 오류를 처리하는 것에 대해 스트레스를받지 않아도됩니다. 해당 범위의 로컬 오류와 같이 처리되는 범위를 묶습니다. 그것은 async / await의 주요 특권 중 하나입니다!
RiqueW

마이크로 벤치 마크는 악마입니다. 숫자를 자세히 살펴보십시오. 여기서 1ms의 차이를 알기 위해 1000x를 수행해야합니다. 예. throw / catch를 추가하면 기능이 최적화되지 않습니다. 그러나 a) 비동기식을 기다리는 경우 백그라운드에서 발생하는 데 0.0005 Ms보다 몇 배 더 걸릴 수 있습니다. b) 여기서 1ms 차이를 만들기 위해 1000x로 수행해야합니다.
Jamie Pate
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.