I am stuck trying to do something simple. I want to be able to count upwards until I click on the screen at which point i want the counting to stop. In reality the code itself will be carrying out complex AI calculations for a game, but first I want to understand how to control the while loop. In android this would be trivial.
Here is what my code looks like
bool OK = true;
main() async{
html.querySelector('#content').onMouseUp.listen((e){
OK = false;
});
await for(int i in naturals){
print(i);
await sleep();
}
}
Stream get naturals async* {
int k = 0; while (OK) { yield await k++; }
}
Future sleep() {
return new Future.delayed(const Duration(milliseconds: 1), () => "1");
}
I put the sleep() method in as a way to ensure that control is passed to the event loop.
Is it possible to control a while loop without the sleep() method?
update
Just enqueue to allow the event queue to process outstanding events without additional delay use
Future sleep() {
return new Future.delayed(Duration.ZERO);
}
original
JavaScript and therefore can't Dart have threads in the browser and you need to pass control back to the event queue to allow it to process other events like you do with sleep().
Another approach would be to use webworkers and run the heavy calculation in a background task (webworker) to keep the UI thread free to process user events. This way even additional CPU cores can be utilized which isn't possible when all code runs in the browsers UI thread.
I don't know how to create webworkers in Dart though except with Angular2 Dart Web workers in Angular 2 Dart where Angular does the initialization.
I don't think it's too difficult but I don't know any docs.
To provide a more general answer - instead of a loop, you want to schedule a sequence of future tasks that each executes one iteration or step of your AI code (or whatever background process you want to have running).
You can have the step task recursively schedule itself:
dynamic doSomething(_) {
print('Doing something ...');
if(!stop) {
new Future.delayed(delay, (){}).then(doSomething);
}
return null;
}
main() async {
doSomething(null);
}
Although I don't recommend doing this. It's awkward to control - the step code has to check a flag variable to see if it should continue or stop and it's free running.
Alternatively you could use a Timer:
void doSomething(Timer timer) {
print('Doing something ...');
}
main() async {
new Timer.periodic(delay, doSomething);
}
This is throttled at a constant rate and has a uniform time step, and is easier to stop (call cancel() on the timer).
Another approach might be to synchronize with the browser's draw refresh cycle by requesting future animation frames:
import 'dart:html';
doSomething(num delta) {
print('Doing something ...');
window.animationFrame.then(doSomething);
}
void main() {
window.animationFrame.then(doSomething);
}
Time steps are not constant but you get the time delta. The advantage of this approach is that animation frame futures will not be scheduled if the browser window is hidden.
See How do I drive an animation loop at 60fps with Dart?
Those are very simple examples. Setting up proper background processes for physics simulation and AI in web games is actually surprisingly (to me at least) non-trivial. Here are two resources I found helpful.
http://gameprogrammingpatterns.com/ - a nice free online book of game programming patterns. http://gameprogrammingpatterns.com/game-loop.html - chapter on game loops.
http://gafferongames.com/game-physics/fix-your-timestep/ - part of a sequence of articles on physics simulation in games.
Following all suggestions this is the code I have ended up with, putting the heavy lifting in a worker, with controllable stopping and starting. I have used simple counting to get this working, but this is being replaced with my complex AI game calculations.
This uses time slices of 100ms within a worker isolate, which can only be interrupted after it has finished the batch. This allows me complete freedom to have complex animations on the screen while all this hard work is being done.
import 'dart:html' as html;
import 'dart:isolate';
import 'dart:math';
SendPort worker;
bool working = false;
main() async{
await startWorker();
html.querySelector('#stage').onMouseUp.listen((e){
if(working)worker.send('stop');
else worker.send('go');
});
}
startWorker() async{
var response = new ReceivePort();
await Isolate.spawnUri(Uri.parse("worker.dart"), null ,response.sendPort)
.then((_) => response.listen((msg){
if(msg is SendPort) {
worker = msg;
}
else {
messageReceived(msg);
}
}));
}
messageReceived(String message){
switch (message){
case 'working': working = true;
break;
case 'idle': working = false;
break;
default : print(message);
break;
}
}
worker.dart
import 'dart:isolate';
import 'dart:async';
SendPort replyTo;
bool OK = false;
const timeSlice = 100;
main(List<String> args, SendPort reply) async{
var response = new ReceivePort();
reply.send(response.sendPort);
replyTo = reply;
response.listen((msg) => messageReceived(msg));
replyTo.send("Hello from worker. Click to start and stop me");
}
messageReceived(String message){
switch(message){
case 'stop':
replyTo.send('idle');
OK = false;
break;
case 'go':
replyTo.send('working');
go();
break;
}
}
go()async {
OK = true;
int i = 0;
batchJob(){
int startTime = new DateTime.now().millisecondsSinceEpoch;
int elapsed = 0;
while(elapsed < timeSlice){
i ++; // the central routine
elapsed = new DateTime.now().millisecondsSinceEpoch - startTime;
}
}
while(OK){
batchJob();
replyTo.send(i.toString());
await new Future.delayed(const Duration(milliseconds: 0), () {});
}
}
Related
I have a function that might take a few seconds to execute, and it is synchronous. Does:
String slowFunction() { ... }
...
Future<String>(() => slowFunction());
change it to asynchronous?
If I need its result in the next step, does this code make sense?
Future<void> anotherFunction() async {
// other async calls with Futures and await
...
final result = await Future<String>(() => slowFunction());
print(result);
...
// do something else with result
}
Seems kind of strange to create a Future only to immediately await on it. Should I just call the function? I guess it kind of 'yields' and allows other code to be executed before, but does such code have any use?
There is no point in making a process that is inherently synchronous and dressing it up as an asynchronous one. This is due to how asynchronicity (more generally referred to as "concurrency") works, not just in Dart but in general. Concurrency is just a programming trick to make multiple operations run interleaved with each other in the same thread, giving the illusion of true parallelism (which is where different threads or processes run simultaneously). This allows processes that would normally block while waiting for a resource to be put off until later as the program carries on with other things.
If you were to take a synchronous process that blocks due to work being actively done, either the program will block anyway as the "async" code executes just as it would otherwise do or the program will block just as long but at a later time. Either way, you are still blocking your program with a long-running process.
Take the following for example, which is what you ask: take a long-running process and wrap it in a Future, thus making it "asynchronous":
String slowFunction() { ... }
...
String result = await Future(slowFunction);
In normal concurrency, this will put slowFunction in the async queue. The next time the program has some downtime (in between UI draw calls, for example) it will pull that function out of the queue and process it. And thats when it will block for 2-3 seconds while the function executes.
In Dart, though, it works slightly differently. Because slowFunction is not an async function and doesn't await anything, Dart will attempt to run it synchronously anyway, in which case you needn't have bothered wrapping it in a Future in the first place.
You have two options here if you want to break up the operation of your synchronous function. Either you have to break it up into distinct operations that you can await between (which is itself a somewhat complicated process, isn't always possible, and is generally a good source of code smell), or you offload the function to a separate thread altogether, employing parallelism rather than mere concurrency.
Dart is single-threaded, but it can be multi-processed through the use of isolates. (An isolate is Dart's name for a child process and is as close to true multithreading that you can get in Dart.) By wrapping your function in an Isolate, you can run the work on an entirely separate process. That way, if that process blocks for 2-3 seconds, it won't affect the bulk of your app at all.
There is a catch, though. Because isolates are completely different processes, there is no sharing of memory whatsoever. That means any data that the isolate has access to has to be manually passed in through the use of "ports", namely SendPort and ReceivePort. This naturally makes isolate programming a bit of a pain, but in exchange, you won't run into things like your program having race conditions or getting deadlocked. (Because of shared memory problems, at least. Strictly speaking, there are plenty of other ways to get deadlocks and race conditions.)
Using an Isolate works like so:
// main process
void createIsolate() async {
ReceivePort isolateToMain = ReceivePort();
isolateToMain.listen((data) {
// Listen for data passed back to the main process
});
Isolate myIsolateInstance = await Isolate.spawn(myIsolate, isolateToMain.sendPort);
}
// isolate process
void myIsolate(SendPort mainToIsolate) {
final result = slowFunction();
mainToIsolate.send(result);
}
I have a function that might take a few seconds to execute, and it is synchronous. Does:
String slowFunction() { ... }
...
Future<String>(() => slowFunction());
change it to asynchronous?
Simply returning a Future will not make your function asynchronous in the way that you probably want.
When you call an asynchronous function, as much of it as possible will be executed synchronously. That is, the function will execute synchronously until it reaches the first await (i.e., until it returns a Future). Since a Dart isolate is single-threaded, if you want other work to be able to occur concurrently with your long-running operation, slowFunction would internally need to use await (which is syntactic sugar for creating Future.then() callbacks) to allow execution to yield.
Consider the following code:
Future<void> longRunningOperation1() async {
for (var i = 0; i < 100000000; i += 1) {
if (i % 10000000 == 0) {
print('longRunningOperation1: $i');
}
}
}
Future<void> longRunningOperation2() async {
for (var i = 0; i < 100000000; i += 1) {
if (i % 10000000 == 0) {
print('longRunningOperation2: $i');
}
}
}
Future<void> main() async {
await Future.wait([longRunningOperation1(), longRunningOperation2()]);
}
You will see that longRunningOperation1 and longRunningOperation2 never overlap; one always runs to completion before the other one starts. To allow the operations to overlap with minimal changes, you could do:
Future<void> longRunningOperation1() async {
for (var i = 0; i < 100000000; i += 1) {
if (i % 10000000 == 0) {
print('longRunningOperation1: $i');
await Future.delayed(Duration.zero);
}
}
}
Future<void> longRunningOperation2() async {
for (var i = 0; i < 100000000; i += 1) {
if (i % 10000000 == 0) {
print('longRunningOperation2: $i');
await Future.delayed(Duration.zero);
}
}
}
I am using a wrapper to spawn slow operations into a separate Isolate and return aFuture. It also allows to pass the function to run and some arguments as well.
import 'dart:async';
import 'dart:isolate';
/// Example
///
/// ```
/// main() async {
/// String str;
/// str = await runAsync<String, String Function(String)>(sing, ["lalalala"]);
/// print(str);
///
/// str = await runAsync<String, Function>(song);
/// print(str);
/// }
///
/// String sing(String str) => "Singing: " + str;
/// String song() => "lololololo";
/// ```
Future<R> runAsync<R, F>(F func, [List<dynamic> parameters]) async {
final receivePort = ReceivePort();
await Isolate.spawn(asyncRunner, receivePort.sendPort);
// The 'asyncRunner' isolate sends it's SendPort as the first message
final sendPort = await receivePort.first;
final responsePort = ReceivePort();
sendPort.send([responsePort.sendPort, func, parameters ?? []]);
final res = await responsePort.first;
if (res is! R)
return Future.error(res);
else if (res == null) return null;
return res as R;
}
// Isolate entry point
void asyncRunner(SendPort sendPort) async {
// Open the ReceivePort for incoming messages
final port = ReceivePort();
// Notify our creator the port we listen to
sendPort.send(port.sendPort);
final msg = await port.first;
// Execute
final SendPort replyTo = msg[0];
final Function myFunc = msg[1];
final List<dynamic> parameters = msg[2] ?? [];
try {
switch (parameters.length) {
case 0:
replyTo.send(myFunc());
break;
case 1:
replyTo.send(myFunc(parameters[0]));
break;
case 2:
replyTo.send(myFunc(parameters[0], parameters[1]));
break;
case 3:
replyTo.send(myFunc(parameters[0], parameters[1], parameters[2]));
break;
case 4:
replyTo.send(
myFunc(parameters[0], parameters[1], parameters[2], parameters[3]));
break;
case 5:
replyTo.send(myFunc(parameters[0], parameters[1], parameters[2],
parameters[3], parameters[4]));
break;
default:
replyTo.send(Exception("Unsupported argument length"));
}
} catch (err) {
replyTo.send(Exception(err.toString()));
}
// Done
port.close();
Isolate.current.kill();
}
https://github.com/vocdoni/dvote-dart/blob/main/lib/util/asyncify.dart#L16
I'm making a Flutter app that using asynchronous a lot but it not working like how I understand about it. So I have some question about async and await in dart. Here is an example:
Future<int> someFunction() async {
int count = 0;
for (int i=0; i< 1000000000;i ++) {
count+= i;
}
print("done");
return count;
}
Future<void> test2() async {
print("begin");
var a = await someFunction();
print('end');
}
void _incrementCounter() {
print("above");
test2();
print("below");
}
test2() function will take a lot of time to done. right? So what i want is when test2 keep his work running until done, everything will keep running and not wait for test2().
When i run the function _incrementCounter(), it show the result:
above begin done below end
The problem is it didn't show "below" right away but it wait until someFunction() done.
This is result i want:
above begin below done end
This is the expected behavior since this change in Dart 2.0 which can be found in the changelog:
(Breaking) Functions marked async now run synchronously until the first await statement. Previously, they would return to the event loop once at the top of the function body before any code runs (issue 30345).
Before I give the solution I want to note you that async code are not running in another thread so the concept of:
keep his work running until done, everything will keep running and not
wait for test2()
Is fine but at some point your application are going to wait for test2() to finish since it is spawned as a task on the job queue where it will not leave other jobs to run before it is done. If you want the experience of no slowdown you want to either split the job into multiple smaller jobs or spawn an isolate (which are running in another thread) to run the calculation and later return the result.
Here is the solution go get your example to work:
Future<int> someFunction() async {
int count = 0;
for (int i=0; i< 1000000000;i ++) {
count+= i;
}
print("done");
return count;
}
Future<void> test2() async {
print("begin");
var a = await Future.microtask(someFunction);
print('end');
}
void _incrementCounter() {
print("above");
test2();
print("below");
}
main() {
_incrementCounter();
}
By using the Future.microtask constructor we are scheduling the someFunction() to be running as another task. This makes it so the "await" are going to wait since it will be the first true instance of an async call.
callbacks or asynchronous methods or other options
A solution to the callback plague is "await" and "async" or more specifacally 'dart:async' library.
Now, what is the cost of asynchrony?
When should we not use them?
What are the other alternatives?
The below is a badly coded non-polymer custom element that acts like a messageBox in desktop environment. It gives me less braces and parenthesis-es but requires the caller to be also async or use "show().then((v){print(v);});" pattern. Should I avoid the pattern like this?
Is callback better? Or there is an even smarter way?
Polling version
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method
///within the callback you can pass on calling append .
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_)=>e.hide());
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
this.hide();
_modal_returns = e.text;
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
this.hidden=false;
await wait_for_input();
print(_modal_returns);
return _modal_returns;
}
wait_for_input() async{
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
void hide(){
this.hidden=true;
}
Future delay() async{
return new Future.delayed(
new Duration(milliseconds: 100));
}
}
Non-polling version
In response to Günter Zöchbauer's wisdom(avoid polling), posting a version that uses a completer. Thanks you as always Günter Zöchbauer:
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
Completer _completer;
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method.
///Use this setter within the callback for
///append. Always call hide() after
///setting modal_returns.
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_){e.hide();});
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
_modal_returns = e.text;
this.hide();
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
_completer = new Completer();
this.hidden=false;
return _completer.future;
}
void hide(){
hidden=true;
_completer?.complete(_modal_returns);
_completer=null;
}
}
Usually there is no question whether async should be used or not. Usually one would try to avoid it. As soon as you call an async API your code goes async without a possibility to choose if you want that or not.
There are situations where async execution is intentionally made async. For example to split up large computation in smaller chunks to not starve the event queue from being processed.
On the server side there are several API functions that allow to choose between sync and async versions. There was an extensive discussion about when to use which. I'll look it up and add the link.
The disadvantages of using async / await instead of .then() should be minimal.
minimal Dart SDK version with async / await support is 1.9.1
the VM needs to do some additional rewriting before the code is executed the first time, but this is usually neglectable.
Your code seems to do polling.
wait_for_input() async {
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
This should be avoided if possible.
It would be better to let the modal manage its hidden state itself (by adding a hide() method for example), then it doesn't have to poll whether it was hidden from the outside.
I want to do the ill-advised and place both an onClick and onDoubleClick on the same element with each type of event resulting in a different action. Specifically on an image, click to advance to the next image, double-click to toggle fullscreen.
Naturally I get two clicks followed by a double-click (though I understand that some browsers only fire one click before the double-click).
I had thought to make it easy on myself and place each event into a buffer (List) - or rather to add the event.type string to a list, then, after a suitable elapse of time, say 250 or 300 milliseconds examine the last item in the buffer and if doubleclick then go fullscreen else advance the length of the list.
I have found that the list only ever has one item, and I have not worked out how to get the timer to work..
Amongst my attempts was this one:
void catchClickEvents(Event e) {
var eventTypes = new List<String>();
eventTypes.add(e.type);
Duration duration = const Duration(milliseconds: 300);
var timeout = new Timer(duration, () => processEvents(eventTypes));
}
void processEvents(List eTypes) {
// just to see what is going on...
print(eTypes);
}
this results in this output printed to the console:
[click]
[click]
[dblclick]
rather than
[click, click, dblclick]
If I slow it down there is a clear delay before those three event types are printed together
So...
The bigger question is
'What is the darty way to distiguish between single and double-click and perform a different action for each?'
The other questions are:
How do I fill a buffer with successive events (and later clear it down) - or even how do I use Dart's Stream of events as a buffer...
What is the real way timeout before examining the contents of the buffer?
(and I guess the final question is 'should I abandon the effort and settle for a conventional set of buttons with glyph-icons?'!)
I'm not sure if IE still has the event sequence explained here (no 2nd click event)
https://stackoverflow.com/a/5511527/217408
If yes you can use a slightly deviated variant of Roberts solution:
library app_element;
import 'dart:html' as dom;
import 'dart:async' as async;
Duration dblClickDelay = new Duration(milliseconds: 500);
async.Timer clickTimer;
void clickHandler(dom.MouseEvent e, [bool forReal = false]) {
if(clickTimer == null) {
clickTimer = new async.Timer(dblClickDelay, () {
clickHandler(e, true);
clickTimer = null;
});
} else if(forReal){
print('click');
}
}
void dblClickHandler(dom.MouseEvent e) {
if(clickTimer != null) {
clickTimer.cancel();
clickTimer = null;
}
print('doubleClick');
}
void main() {
dom.querySelector('button')
..onClick.listen(clickHandler)
..onDoubleClick.listen(dblClickHandler);
}
or just use Roberts solution with the mouseUp event instead of the click event.
The problem is that your variable is not global.
var eventTypes = new List<String>();
void catchClickEvents(Event e) {
eventTypes.add(e.type);
Duration duration = const Duration(milliseconds: 300);
var timeout = new Timer(duration, () => processEvents(eventTypes));
}
void processEvents(List eTypes) {
print(eTypes);
}
There also is e.detail that should return the number of the click. You can use that, if you don't need the Internet Explorer. The problem with your list is that it grows and never gets cleared.
Let's take into account what we know: You get click events and at somepoint you have doubleclicks.
You could use a click counter here. (Or use e.detail) to skip the second click event. So you only have click and dblclick.
Now when you get a click event, you create a new timer like you did before and run the click action. If you get the dblclick event you simply run you action. This could like this:
DivElement div = querySelector('#div');
Timer timeout = null;
div.onClick.listen((MouseEvent e) {
if(e.detail >= 2) {
e.preventDefault();
} else {
if(timeout != null) {
timeout.cancel();
}
timeout = new Timer(new Duration(milliseconds: 150), () => print('click'));
}
});
div.onDoubleClick.listen((MouseEvent e) {
if(timeout != null) {
timeout.cancel();
timeout = null;
}
print('dblclick');
});
This is the example code that works for me. If you can't rely on e.detail just us a counter and reset it after some ms after a click event.
I hope this helps you.
Regards, Robert
Your page should react on user inputs as fast as possible. If you wait to confirm double click - your onClick will become much less responsive. You can hide the problem by changing visual state of the element(for example, playing animation) after first click but it can confuse user. And it gets even worse with handheld. Also if element has to react only on onClick event, you can "cheat" and listen to onmousedown instead - it will make your UI much more responsive.
On top of all this, double click, from client to client, may have noticeably different trigger time interval and it isn't intuitive, for user, that you can double click something. You will have to bloat your interface with unnecessary hints.
edit: Added my solution. It should be fairly extensible and future proof.
import 'dart:html';
import 'dart:async';
import 'dart:math';
//enum. Kinda... https://code.google.com/p/dart/issues/detail?id=88
class UIAction {
static const NEXT = const UIAction._(0);
static const FULLSCREEN = const UIAction._(1);
static const ERROR = const UIAction._(2);
final int value;
const UIAction._(this.value);
}
/**
*[UIEventToUIAction] transforms UIEvents into corresponding UIActions.
*/
class UIEventToUIAction implements StreamTransformer<UIEvent, UIAction> {
/**
* I use "guesstimate" value for [doubleClickInterval] but you can calculate
* comfortable value for the user from his/her previous activity.
*/
final Duration doubleClickInterval = const Duration(milliseconds: 400);
final StreamController<UIAction> st = new StreamController();
Stream<UIAction> bind(Stream<UIEvent> originalStream) {
int t1 = 0,
t2 = 0;
bool isOdd = true;
Duration deltaTime;
originalStream.timeout(doubleClickInterval, onTimeout: (_) {
if ((deltaTime != null) && (deltaTime >= doubleClickInterval)) {
st.add(UIAction.NEXT);
}
}).forEach((UIEvent uiEvent) {
isOdd ? t1 = uiEvent.timeStamp : t2 = uiEvent.timeStamp;
deltaTime = new Duration(milliseconds: (t1 - t2).abs());
if (deltaTime < doubleClickInterval) st.add(UIAction.FULLSCREEN);
isOdd = !isOdd;
});
return st.stream;
}
}
void main() {
//Eagerly perform time consuming tasks to decrease the input latency.
Future NextImageLoaded;
Future LargeImageLoaded;
element.onMouseDown.forEach((_) {
NextImageLoaded = asyncActionStub(
"load next image. Returns completed future if already loaded");
LargeImageLoaded = asyncActionStub(
"load large version of active image to show in fullscreen mode."
"Returns completed future if already loaded");
});
Stream<UIEvent> userInputs = element.onClick as Stream<UIEvent>;
userInputs.transform(new UIEventToUIAction()).forEach((action) {
switch (action) {
case UIAction.FULLSCREEN:
LargeImageLoaded.then( (_) =>asyncActionStub("fullscreen mode") )
.then((_) => print("'full screen' finished"));
break;
case UIAction.NEXT:
NextImageLoaded.then( (_) =>asyncActionStub("next image") )
.then((_) => print("'next image' finished"));
break;
default:
asyncActionStub("???");
}
});
}
final DivElement element = querySelector("#element");
final Random rng = new Random();
final Set performed = new Set();
/**
*[asyncActionStub] Pretends to asynchronously do something usefull.
* Also pretends to use cache.
*/
Future asyncActionStub(String msg) {
if (performed.contains(msg)) {
return new Future.delayed(const Duration(milliseconds: 0));
}
print(msg);
return new Future.delayed(
new Duration(milliseconds: rng.nextInt(300)),
() => performed.add(msg));
}
Is there a Streams equivalent to Observable.Throttle? If not -- is there any reasonably elegant way to achieve a similar effect?
There's no such method on streams for now. A enhancement request has been filed, you can star issue 8492.
However, you can do that with the where method. In the following exemple, I have defined a ThrottleFilter class to ignore events during a given duration :
import 'dart:async';
class ThrottleFilter<T> {
DateTime lastEventDateTime = null;
final Duration duration;
ThrottleFilter(this.duration);
bool call(T e) {
final now = new DateTime.now();
if (lastEventDateTime == null ||
now.difference(lastEventDateTime) > duration) {
lastEventDateTime = now;
return true;
}
return false;
}
}
main() {
final sc = new StreamController<int>();
final stream = sc.stream;
// filter stream with ThrottleFilter
stream.where(new ThrottleFilter<int>(const Duration(seconds: 10)).call)
.listen(print);
// send ints to stream every second, but ThrottleFilter will give only one int
// every 10 sec.
int i = 0;
new Timer.repeating(const Duration(seconds:1), (t) { sc.add(i++); });
}
The rate_limit package provides throttling and debouncing of Streams.
#Victor Savkin's answer is nice, but I always try to avoid reinventing the wheel. So unless you really only need that throttle I'd suggest using the RxDart package. Since you are dealing with Streams and other reactive objects RxDart has a lot of nice goodies to offer besides throttling.
We can achieve a 500 millisecond throttle several ways:
throttleTime from ThrottleExtensions<T> Stream<T> extensions: stream.throttleTime(Duration(milliseconds: 500)).listen(print);
Combining ThrottleStreamTransformer with TimerStream: stream.transform(ThrottleStreamTransformer((_) => TimerStream(true, const Duration(milliseconds: 500)))).listen(print);
Using Debounce Extensions / DebounceStreamTransformer: stream.debounceTime(Duration(milliseconds: 500)).listen(print);
There are some subtle differences regarding delays, but all of them throttles. As an example about throttleTime vs. debounceTime see What is the difference between throttleTime vs debounceTime in RxJS and when to choose which?
The following version is closer to what Observable.Throttle does:
class Throttle extends StreamEventTransformer {
final duration;
Timer lastTimer;
Throttle(millis) :
duration = new Duration(milliseconds : millis);
void handleData(event, EventSink<int> sink) {
if(lastTimer != null){
lastTimer.cancel();
}
lastTimer = new Timer(duration, () => sink.add(event));
}
}
main(){
//...
stream.transform(new Throttle(500)).listen((_) => print(_));
//..
}