Dart 3.10 is taking off with dot shorthands, stable build hooks, nuanced deprecation annotations, and more!Learn more
Futures and error handling
The Dart language has nativeasynchrony support, making asynchronous Dart code much easier to read and write. However, some code—especially older code—might still useFuture methods such asthen(),catchError(), andwhenComplete().
This page can help you avoid some common pitfalls when using those Future methods.
You don't need this page if your code uses the language's asynchrony support:async,await, and error handling using try-catch. For more information, see theasynchronous programming tutorial.
The Future API and callbacks
#Functions that use the Future API register callbacks that handle the value (or the error) that completes a Future. For example:
myFunc().then(processValue).catchError(handleError); The registered callbacks fire based on the following rules:then()'s callback fires if it is invoked on a Future that completes with a value;catchError()'s callback fires if it is invoked on a Future that completes with an error.
In the example above, ifmyFunc()'s Future completes with a value,then()'s callback fires. If no new error is produced withinthen(),catchError()'s callback does not fire. On the other hand, ifmyFunc() completes with an error,then()'s callback does not fire, andcatchError()'s callback does.
Examples of using then() with catchError()
# Chainedthen() andcatchError() invocations are a common pattern when dealing with Futures, and can be thought of as the rough equivalent of try-catch blocks.
The next few sections give examples of this pattern.
catchError() as a comprehensive error handler
# The following example deals with throwing an exception from withinthen()'s callback and demonstratescatchError()'s versatility as an error handler:
myFunc().then((value){doSomethingWith(value);...throwException('Some arbitrary error');}).catchError(handleError); IfmyFunc()'s Future completes with a value,then()'s callback fires. If code withinthen()'s callback throws (as it does in the example above),then()'s Future completes with an error. That error is handled bycatchError().
IfmyFunc()'s Future completes with an error,then()'s Future completes with that error. The error is also handled bycatchError().
Regardless of whether the error originated withinmyFunc() or withinthen(),catchError() successfully handles it.
Error handling within then()
# For more granular error handling, you can register a second (onError) callback withinthen() to handle Futures completed with errors. Here isthen()'s signature:
Future<R>then<R>(FutureOr<R>Function(Tvalue)onValue,{Function?onError}); Register the optional onError callback only if you want to differentiate between an error forwardedtothen(), and an error generatedwithinthen():
asyncErrorFunction().then(successCallback,onError:(e){handleError(e);// Original error.anotherAsyncErrorFunction();// Oops, new error.},).catchError(handleError);// Error from within then() handled. In the example above,asyncErrorFunction()'s Future's error is handled with theonError callback;anotherAsyncErrorFunction() causesthen()'s Future to complete with an error; this error is handled bycatchError().
In general, implementing two different error handling strategies is not recommended: register a second callback only if there is a compelling reason to catch the error withinthen().
Errors in the middle of a long chain
# It is common to have a succession ofthen() calls, and catch errors generated from any part of the chain usingcatchError():
Future<String>one()=>Future.value('from one');Future<String>two()=>Future.error('error from two');Future<String>three()=>Future.value('from three');Future<String>four()=>Future.value('from four');voidmain(){one()// Future completes with "from one"..then((_)=>two())// Future completes with two()'s error..then((_)=>three())// Future completes with two()'s error..then((_)=>four())// Future completes with two()'s error..then((value)=>value.length)// Future completes with two()'s error..catchError((e){print('Got error:$e');// Finally, callback fires.return42;// Future completes with 42.}).then((value){print('The value is$value');});}// Output of this program:// Got error: error from two// The value is 42 In the code above,one()'s Future completes with a value, buttwo()'s Future completes with an error. Whenthen() is invoked on a Future that completes with an error,then()'s callback does not fire. Instead,then()'s Future completes with the error of its receiver. In our example, this means that aftertwo() is called, the Future returned by every subsequentthen()completes withtwo()'s error. That error is finally handled withincatchError().
Handling specific errors
#What if we want to catch a specific error? Or catch more than one error?
catchError() takes an optional named argument,test, that allows us to query the kind of error thrown.
Future<T>catchError(FunctiononError,{boolFunction(Objecterror)?test}); ConsiderhandleAuthResponse(params), a function that authenticates a user based on the params provided, and redirects the user to an appropriate URL. Given the complex workflow,handleAuthResponse() could generate various errors and exceptions, and you should handle them differently. Here's how you can usetest to do that:
voidmain(){handleAuthResponse(const{'username':'dash','age':3}).then((_)=>...).catchError(handleFormatException,test:(e)=>eisFormatException).catchError(handleAuthorizationException,test:(e)=>eisAuthorizationException,);}Async try-catch-finally using whenComplete()
# Ifthen().catchError() mirrors a try-catch,whenComplete() is the equivalent of 'finally'. The callback registered withinwhenComplete() is called whenwhenComplete()'s receiver completes, whether it does so with a value or with an error:
finalserver=connectToServer();server.post(myUrl,fields:const{'name':'Dash','profession':'mascot'}).then(handleResponse).catchError(handleError).whenComplete(server.close); We want to callserver.close regardless of whetherserver.post() produces a valid response, or an error. We ensure this happens by placing it insidewhenComplete().
Completing the Future returned by whenComplete()
# If no error is emitted from withinwhenComplete(), its Future completes the same way as the Future thatwhenComplete() is invoked on. This is easiest to understand through examples.
In the code below,then()'s Future completes with an error, sowhenComplete()'s Future also completes with that error.
voidmain(){asyncErrorFunction()// Future completes with an error:.then((_)=>print("Won't reach here"))// Future completes with the same error:.whenComplete(()=>print('Reaches here'))// Future completes with the same error:.then((_)=>print("Won't reach here"))// Error is handled here:.catchError(handleError);} In the code below,then()'s Future completes with an error, which is now handled bycatchError(). BecausecatchError()'s Future completes withsomeObject,whenComplete()'s Future completes with that same object.
voidmain(){asyncErrorFunction()// Future completes with an error:.then((_)=>...).catchError((e){handleError(e);printErrorMessage();returnsomeObject;// Future completes with someObject}).whenComplete(()=>print('Done!'));// Future completes with someObject}Errors originating within whenComplete()
# IfwhenComplete()'s callback throws an error, thenwhenComplete()'s Future completes with that error:
voidmain(){asyncErrorFunction()// Future completes with a value:.catchError(handleError)// Future completes with an error:.whenComplete(()=>throwException('New error'))// Error is handled:.catchError(handleError);}Potential problem: failing to register error handlers early
#It is crucial that error handlers are installed before a Future completes: this avoids scenarios where a Future completes with an error, the error handler is not yet attached, and the error accidentally propagates. Consider this code:
voidmain(){Future<Object>future=asyncErrorFunction();// BAD: Too late to handle asyncErrorFunction() exception.Future.delayed(constDuration(milliseconds:500),(){future.then(...).catchError(...);});} In the code above,catchError() is not registered until half a second afterasyncErrorFunction() is called, and the error goes unhandled.
The problem goes away ifasyncErrorFunction() is called within theFuture.delayed() callback:
voidmain(){Future.delayed(constDuration(milliseconds:500),(){asyncErrorFunction().then(...).catchError(...);// We get here.});}Potential problem: accidentally mixing synchronous and asynchronous errors
#Functions that return Futures should almost always emit their errors in the future. Since we do not want the caller of such functions to have to implement multiple error-handling scenarios, we want to prevent any synchronous errors from leaking out. Consider this code:
Future<int>parseAndRead(Map<String,dynamic>data){finalfilename=obtainFilename(data);// Could throw.finalfile=File(filename);returnfile.readAsString().then((contents){returnparseFileData(contents);// Could throw.});} Two functions in that code could potentially throw synchronously:obtainFilename() andparseFileData(). BecauseparseFileData() executes inside athen() callback, its error does not leak out of the function. Instead,then()'s Future completes withparseFileData()'s error, the error eventually completesparseAndRead()'s Future, and the error can be successfully handled bycatchError().
ButobtainFilename() is not called within athen() callback; ifit throws, a synchronous error propagates:
voidmain(){parseAndRead(data).catchError((e){print('Inside catchError');print(e);return-1;});}// Program Output:// Unhandled exception:// <error from obtainFilename>// ... Because usingcatchError() does not capture the error, a client ofparseAndRead() would implement a separate error-handling strategy for this error.
Solution: Using Future.sync() to wrap your code
# A common pattern for ensuring that no synchronous error is accidentally thrown from a function is to wrap the function body inside a newFuture.sync() callback:
Future<int>parseAndRead(Map<String,dynamic>data){returnFuture.sync((){finalfilename=obtainFilename(data);// Could throw.finalfile=File(filename);returnfile.readAsString().then((contents){returnparseFileData(contents);// Could throw.});});} If the callback returns a non-Future value,Future.sync()'s Future completes with that value. If the callback throws (as it does in the example above), the Future completes with an error. If the callback itself returns a Future, the value or the error of that Future completesFuture.sync()'s Future.
With code wrapped withinFuture.sync(),catchError() can handle all errors:
voidmain(){parseAndRead(data).catchError((e){print('Inside catchError');print(e);return-1;});}// Program Output:// Inside catchError// <error from obtainFilename>Future.sync() makes your code resilient against uncaught exceptions. If your function has a lot of code packed into it, chances are that you could be doing something dangerous without realizing it:
FuturefragileFunc(){returnFuture.sync((){finalx=someFunc();// Unexpectedly throws in some rare cases.vary=10/x;// x should not equal 0....});}Future.sync() not only allows you to handle errors you know might occur, but also prevents errors fromaccidentally leaking out of your function.
More information
#See theFuture API reference for more information on Futures.
Unless stated otherwise, the documentation on this site reflects Dart 3.10.0. Page last updated on 2025-2-12.View source orreport an issue.