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

Time:12-28

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.

CodePudding user response:

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.

CodePudding user response:

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.

  • Related