How to reject in async/await syntax?

后端 未结 7 1739
无人及你
无人及你 2020-11-28 17:56

How can I reject a promise that returned by an async/await function?

e.g. Originally:

         


        
相关标签:
7条回答
  • 2020-11-28 18:06

    I have a suggestion to properly handle rejects in a novel approach, without having multiple try-catch blocks.

    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 the to.ts function should be imported from:

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

    Credits go to Dima Grossman in the following link.

    0 讨论(0)
  • 2020-11-28 18:15

    I know this is an old question, but I just stumbled across the thread and there seems to be a conflation here between errors and rejection that runs afoul (in many cases, at least) of the oft-repeated advice not to use exception handling to deal with anticipated cases. To illustrate: if an async method is trying to authenticate a user and the authentication fails, that's a rejection (one of two anticipated cases) and not an error (e.g., if the authentication API was unavailable.)

    To make sure I wasn't just splitting hairs, I ran a performance test of three different approaches to that, using this code:

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

    Some of the stuff that's in there is included because of my uncertainty regarding the Javascript interpreter (I only like to go down one rabbit hole at a time); for instance, I included the doSomething function and assigned its return to dummyValue to ensure that the conditional blocks wouldn't get optimized out.

    My results were:

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

    I know that there are plenty of cases where it's not worth the trouble to hunt down small optimizations, but in larger-scale systems these things can make a big cumulative difference, and that's a pretty stark comparison.

    SO… while I think the accepted answer's approach is sound in cases where you're expecting to have to handle unpredictable errors within an async function, in cases where a rejection simply means "you're going to have to go with Plan B (or C, or D…)" I think my preference would be to reject using a custom response object.

    0 讨论(0)
  • 2020-11-28 18:16

    You can create a wrapper function that takes in a promise and returns an array with data if no error and the error if there was an error.

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

    Use it like this in ES7 and in an async function:

    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
    }
    
    0 讨论(0)
  • 2020-11-28 18:22

    It should probably also be mentioned that you can simply chain a catch() function after the call of your async operation because under the hood still a promise is returned.

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

    This way you can avoid the try/catch syntax if you do not like it.

    0 讨论(0)
  • 2020-11-28 18:25

    This is not an answer over @T.J. Crowder's one. Just an comment responding to the comment "And actually, if the exception is going to be converted to a rejection, I'm not sure whether I am actually bothered if it's an Error. My reasons for throwing only Error probably don't apply."

    if your code is using async/await, then it is still a good practice to reject with an Error instead of 400:

    try {
      await foo('a');
    }
    catch (e) {
      // you would still want `e` to be an `Error` instead of `400`
    }
    
    0 讨论(0)
  • 2020-11-28 18:26

    Your best bet is to throw an Error wrapping the value, which results in a rejected promise with an Error wrapping the value:

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

    You can also just throw the value, but then there's no stack trace information:

    } catch (error) {
        throw 400;
    }
    

    Alternately, return a rejected promise with an Error wrapping the value, but it's not idiomatic:

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

    (Or just return Promise.reject(400);, but again, then there's no context information.)

    In your case, as you're using TypeScript and foo's return value is Promise<A>, you'd use this:

    return Promise.reject<A>(400 /*or Error*/ );
    

    In an async/await situation, that last is probably a bit of a semantic mis-match, but it does work.

    If you throw an Error, that plays well with anything consuming your foo's result with await syntax:

    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`.
    }
    
    0 讨论(0)
提交回复
热议问题