-
Notifications
You must be signed in to change notification settings - Fork 1.7k
[change] loadLibrary
should return FutureOr
#53410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
SGTM! I'm not (too) worried about |
@gmpassos Could you explain the practical impact of the optimization? Implicit in this request is that |
Note that it is possible to generalize the work-around to one piece of code that works for all deferred imports. import 'dart:async';
import 'dart:typed_data' deferred as typed_data;
typedef LoadFunction = Future<void> Function();
FutureOr<R> callWithLoadedLibrary<R>(LoadFunction loader, R Function() action) {
print('callWithLoadedLibrary');
if (_loaded.contains(loader)) return action();
print('loading');
return loader().then((_) {
print('loaded');
_loaded.add(loader);
return action();
});
}
final Set<LoadFunction> _loaded = {};
main() async {
final r1 = await callWithLoadedLibrary(typed_data.loadLibrary, () {
print('f1');
return typed_data.Uint8List(3);
});
final f2 = callWithLoadedLibrary(typed_data.loadLibrary, () {
print('f2');
return typed_data.Uint8List(5);
});
if (f2 is Future) throw 'unexpected';
print('done $r1 $f2');
}
} prints:
/cc @lrhn |
I'm generally opposed to giving Either you don't know whether it's done, and therefore you need to have the async code path anyway, on top of the synchronous "optimized" part, or you do know that it's done, because someone checked already, but haven't cached it's result. Having two interleaved code paths, one sync and one async, doing the same computation, is unlikely to be worth the code size increase. And complexity. And testing trouble, to make sure all the combinations are tested. If it is worth it, it's because the code is running very often, and then it's better to have a single wait guarding access to everything, and all synchronous code behind that one wait. That's what the example code here does: Do an async operation once, then cache that it was done. Using multiple functions returning |
First and foremost, thank you for the quick response and for participating in this discussion. Here's a pratical example: https://github.com/gmpassos/deferred_lib_issue (The issue only happens when running from In this example, you can observe how to create a straightforward framework that accommodates the use of both Just run the webdev serve --launch-in-chrome -r I believe the primary focus of our discussion should not solely revolve around performance but should also emphasize ease of use. I recommend attempting to write code that minimizes the excessive use of I make a conscious effort to minimize the usage of The consequence of having numerous When working with a deferred library that utilizes a non-asynchronous prefix, it introduces a dependency on Creating an application that truly adheres to import compartmentalization can be challenging. However, using a deferred library with numerous exported elements can also be complex due to the hidden asynchronous flow inherent in the deferred pattern. One thing I truly appreciate about Dart is that it doesn't impose a specific programming paradigm but rather provides you with the best tools for each use case. In the latest updates to the language, we've seen advancements in both Functional Programming and Object-Oriented capabilities. This is remarkable, especially considering that many languages tend to segregate paradigms rather than unify them. However, when it comes to discussing I am the author of the |
Using The deferred library is an example of this. You say that it looks like something that could be modelled by Using (I'd probably design around a deferred load by creating an object which provides access to the loaded library, and guard creation of that object by waiting on the |
Here is a code snippet derived from a real application to illustrate the concept: class MainView {
FutureOr<R> business_call<R>(R Function() call) {
// ... handles a deferred library then executes call(), avoiding `Future`
}
FutureOr<View> _renderRouteComponent(String? route) {
switch (route) {
case 'home':
return ViewHome(content);
case 'login':
return ViewLogin(content);
case 'register':
return ViewRegister(content);
case 'user':
return ViewUser(content);
case 'emailVerification':
return ViewEmailVerification(content);
case 'network':
return business_call(() => business.ViewNetwork(content));
case 'section':
return business_call(() => business.ViewSection(content));
case 'item':
return business_call(() => business.ViewItem(content));
case 'business_account':
return business_call(() => business.ViewBusinessAccount(content));
case 'terms':
return ViewTerms(content);
case 'addresses':
return business_call(() => business.ViewAddresses(content));
case 'payments':
return business_call(() => business.ViewPayments(content));
case 'dashboard':
return business_call(() => business.ViewDashboard(content));
case 'financial':
return business_call(() => business.ViewFinancial(content));
case 'payment_broker':
return business_call(() => business.ViewPaymentBroker(content));
case 'system_integration':
return business_call(
() => business.ViewSystemIntegration(content));
default:
return ViewHome(content);
}
}
} Before deferring a library, it was a standard switch statement with a regular return for each case. I don't believe that migrating regular code to work with a deferred library should require many changes, such as moving everything to a new class. It would be better for it to remain similar to the natural code (without a deferred library). I really need to avoid returning a The framework expects a Please note that, for now, deferred libraries are only supported by Dart code compiled to JavaScript. We utilize them in a browser environment, which is significantly impacted by performance considerations. |
IMHO, |
Your use of We generally discourage using Here, you are providing either a future or a value to pass into a framework API, which is generous enough to accept both. In this case, it even has a visible difference in behavior depending on whether you pass it a value or a future. That's untraditional, but in this use-case its reasonable. The alternative would be to have two APIs, one Usually when a framework asks for a That doesn't mean that If you want to know whether it has been called, and the returned future has completed, it's just a matter of caching. Future<void>? Function() libraryLoad = () {
bool done = false;
final Future<void> future = prefix.loadLibrary().whenComplete(() {
done = true
});
return () => done ? null : future;
} (); then you can use Can be made reusable, and configurable (do you want to start loading eagerly when someone creates the function, /// Creates a function which caches the result of loading a deferred library.
///
/// When the returned function is called for the first time, the [prefixLoadLibrary] is called,
/// and the future it returns is returned.
/// Further calls of the returned function will return the same future, until that future completes.
/// If the future completes with a value, any further calls to the returned function will return `null`
/// immediately and synchonously.
/// If the future completes with an error, the returned function will keep returning the same future,
/// with that error, to all later callers.
///
/// The intended use, and where the naming comes from, is to cache the result of the `loadLibrary`
/// function of a deferred library:
/// ```dart
/// import 'package:example/example.dart' deferred as ex;
/// // ...
/// final loadEx = cacheDeferredLibrary(ex.loadLibrary);
/// ```
/// which can then be used as:
/// ```dart
/// if (loadEx() case var future?) await future;
/// ```
/// to wait only if there is a future to wait on, and continue immediately
/// if the library has already been loaded through that function.
///
/// To trigger loading of the library early, if you know that you'll want to use it later,
/// you can call the function, and ignore any errors, like `loadEx()?.ignore()`.
FutureOr<void?> Function() cacheDeferredLibrary(Future<void> Function() prefixLoadLibrary) {
bool done = false;
Future<void>? future;
return () => done ? null : (future ??= (prefixLoadLibrary()..then<void>(
(_) { done = true; },
onError: (_, _) => future = null)));
} Your helper function, which runs a callback synchronously or asynchronously, may also be written as an extension method: import "dart:async";
extension FutureOrThen<T> on FutureOr<T> {
/// Calls [onValue] on the value of this [FutureOr].
///
/// Works like [Future.then] on a `Future<T>`, and for the remaining `T` values,
/// the result of calling `onValue` with that value is returned directly.
FutureOr<R> then<R>(
FutureOr<R> Function(T) onValue, {
FutureOr<R> Function(Object, StackTrace)? onError,
}) => switch (this) {
Future<T> f => f.then<R>(onValue, onError: onError),
T v => onValue(v),
};
} (An extension on |
Gratitude for your in-depth response. I think that the solution could be just some improvements on the standard features ( To really accomplish what we do (avoid https://pub.dev/documentation/async_extension/latest/async_extension/FutureOrExtension.html I really think that add at least a standard |
No language change plans, and if there were any, they would be in the repo in issues like dart-lang/language#2033. |
Change Intent
The
loadLibrary
, used to ensure that a deferred library is load, should return aFutureOr
for optimization purpose.Justification
If
loadLibrary
returns aFutureOr
will be possible to avoid waiting of aFuture
already resolved.Here is a current workaround that avoids
await
to call a code that depends on a deferred library:With the proposed change:
Impact
Current Dart code won't be affected if using
await
.Only a code using
then
will need to be changed.Mitigation
FutureOr.then
could also be provided bydart:async
, improving usage ofFutureOr
.If
FutureOr.then
is also provided, no change in current code will be needed.Change Timeline
No deprecation is needed.
Associated CLs
None
The text was updated successfully, but these errors were encountered: