简体   繁体   中英

How to handle errors in method that has IAsyncEnumerable as return type

I have an API endpoint:

[HttpGet("api/query")]
public async IAsyncEnumerable<dynamic> Query(string name)
{    
   await foreach(var item in _myService.CallSomethingReturningAsyncStream(name))
   {
       yield return item;
   }
}

I would like to be able to in case of an ArgumentException return something like "Bad request" response.

If I try using try-catch block, I get error :

CS1626: Cannot yield a value in the body of a try block with a catch clause

Please note that it is an API endpoint method, so error handling should ideally be in the same method, without need for making additional middlewares.

You have to split method. Extract part which does async processing:

private async IAsyncEnumerable<dynamic> ProcessData(TypeOfYourData data)
{    
   await foreach(var item in data)
   {
       yield return item;
   }
}

And then in API method do:

[HttpGet("api/query")]
public IActionResult Query(string name)
{    
   TypeOfYourData data;
   try {
       data = _myService.CallSomethingReturningAsyncStream(name);
   }
   catch (...) {
        // do what you need
        return BadRequest();
   }
   return Ok(ProcessData(data));
}

Or actually you can just move the whole thing into separate method:

[HttpGet("api/query")]
public IActionResult Query(string name)
{           
   try {
       return Ok(TheMethodYouMovedYourCurrentCodeTo);
   }
   catch (...) {
        // do what you need
        return BadRequest();
   }
}

It will of course only catch exceptions thrown before actual async enumeration starts, but that's fine for your use case as I understand. Returning bad request after async enumeration has started is not possible, because response is already being sent to client.

You could install the System.Interactive.Async package, and do this:

[HttpGet("api/query")]
public IAsyncEnumerable<dynamic> Query(string name)
{
    return AsyncEnumerableEx.Defer(() => _myService.CallSomethingReturningAsyncStream(name))
        .Catch<dynamic, Exception>(ex =>
            AsyncEnumerableEx.Return<dynamic>($"Bad request: {ex.Message}"));
}

The signature of the Defer operator:

// Returns an async-enumerable sequence that invokes the specified
// factory function whenever a new observer subscribes.
public static IAsyncEnumerable<TSource> Defer<TSource>(
    Func<IAsyncEnumerable<TSource>> factory)

The signature of the Catch operator:

// Continues an async-enumerable sequence that is terminated by
// an exception of the specified type with the async-enumerable sequence
// produced by the handler.
public static IAsyncEnumerable<TSource> Catch<TSource, TException>(
    this IAsyncEnumerable<TSource> source,
    Func<TException, IAsyncEnumerable<TSource>> handler);

The signature of the Return operator:

// Returns an async-enumerable sequence that contains a single element.
public static IAsyncEnumerable<TValue> Return<TValue>(TValue value)

The Defer might seem superficial, but it is needed for the case that the _myService.CallSomethingReturningAsyncStream throws synchronously. In case this method is implemented as an async iterator , it will never throw synchronously, so you could omit the Defer .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM