为何两个Dart函数仅一个在catchError返回Future<String?>时报错?
问题
我在同一个类里编写了两个Dart函数,其中_createOrUpdateUser函数的catchError回调中返回Future<String?>.error(e)时触发如下错误:
A value of type 'Future<String?>' can't be returned by the 'onError' handler because it must be assignable to 'FutureOr
'.
但另一个_authUser函数使用相同写法却没有问题,想搞清楚其中原因。
以下是两个函数的代码:
/// Creates or update the User collection Future<String?> _createOrUpdateUser(User user) { final email = user.email; final nameFromEmail = email!.substring(0, email.indexOf('@')).replaceAll(".", " ").trim(); return FirebaseFirestore.instance.collection('users').doc(user.uid).set({ 'id': user.uid, 'screenName': '', 'displayName': user.displayName ?? nameFromEmail, 'photoUrl': user.photoURL, 'bio': '', 'darkMode': false, 'email': user.email, }).then((_) { debugPrint("User ${user.displayName} is created"); return null; }).catchError((e) { debugPrint(e.toString()); return Future<String?>.error(e); // <<<<<< Error here }); } Future<String?> _authUser(BuildContext context, LoginData data) { debugPrint('Name: ${data.name}, Password: ${data.password}'); return auth .signInWithEmailAndPassword(email: data.name, password: data.password) .then((credential) { try { final user = credential.user; if (user == null) { return 'Password does not match'; } else { _createOrUpdateUser(user); // == Tell the app we just signed in Provider.of<StateModel>(context, listen: false).signedIn = user; return null; } } catch (e) { return 'User not exists'; } }).catchError((e) { if (e.code == 'weak-password') { debugPrint('The password provided is too weak.'); } else if (e.code == 'email-already-in-use') { debugPrint('The account already exists for that email.'); } else { debugPrint(e.toString()); } return Future<String?>.error(e); // <<<<<< No problem here }); }
解答
核心原因:catchError的返回类型由上游Future的类型决定
两个函数的差异本质是上游Future的返回类型不同:
- 在
_createOrUpdateUser中,set()方法返回Future<void>,后续的.then((_) { return null; })把这个Future转换成了Future<Null>。因此对应的catchError回调要求返回FutureOr<Null>(即Null或Future<Null>),但你返回的是Future<String?>,类型不匹配导致报错。 - 在
_authUser中,signInWithEmailAndPassword返回Future<UserCredential>,后续的.then(...)回调可能返回String或Null,因此这个then返回的是Future<String?>。对应的catchError回调要求返回FutureOr<String?>,你返回的Future<String?>刚好符合类型要求,所以没有报错。
修复方案
有三种常用方式解决_createOrUpdateUser的错误:
方式1:直接抛出错误,替代返回Future.error
catchError回调中直接抛出异常,Dart会自动将其包装成Future.error,符合Future<Null>的错误处理逻辑:
.catchError((e) { debugPrint(e.toString()); throw e; // 直接抛出异常 });
方式2:对齐上游Future与函数的返回类型
因为函数声明返回Future<String?>,可以在then里显式返回String?类型的值,把上游Future<void>转换成Future<String?>,此时catchError的返回类型要求变为FutureOr<String?>,返回Future<String?>.error(e)就合法了:
.then((_) { debugPrint("User ${user.displayName} is created"); return null as String?; // 显式指定返回类型为String? }) .catchError((e) { debugPrint(e.toString()); return Future<String?>.error(e); });
方式3:用async/await重构代码(推荐)
链式调用的类型问题在async/await写法下会自动消失,代码可读性也更高:
Future<String?> _createOrUpdateUser(User user) async { final email = user.email; final nameFromEmail = email!.substring(0, email.indexOf('@')).replaceAll(".", " ").trim(); try { await FirebaseFirestore.instance.collection('users').doc(user.uid).set({ 'id': user.uid, 'screenName': '', 'displayName': user.displayName ?? nameFromEmail, 'photoUrl': user.photoURL, 'bio': '', 'darkMode': false, 'email': user.email, }); debugPrint("User ${user.displayName} is created"); return null; } catch (e) { debugPrint(e.toString()); throw e; // 直接抛出,函数自动返回Future.error(e) } }
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

