I am trying to detect when a task takes a long time using redux-saga.
SomeHeavyAsyncFunc is a task with a varying time consumption, from a few ms to some seconds.
I want to trigger an action when it takes more than a certain amount of time, to show a loader.
However I don't want to race with a delay, I simply want to trigger the loading action only if necessary but I don't want to cancel the action even if its long.
Here is an excerpt of my idea:
const timeOut = yield fork(function* () {
yield call(delay, 300);
yield put(isPending());
});
// in parallel run the computation
// it will cancel the timeout after computation is done
const task = yield fork(function* () {
const res = yield call(SomeHeavyAsyncFunc);
yield cancel(timeOut);
yield put(success());
});
The timeout run in the background. If it is over, it simply put an action that will show a loader. The timeout is cancelled when the task is done, so the loader is not shown if the task was actually fast.
However, this does not seem to work. The pending action is triggered only if the action more than 300ms in this exemple. However, it is not triggered as soon as the delay is over but later, so the loader only show up very late.
Does somebody have a working example on this ? Maybe my saga is wrong but what I ask is simply undoable using redux (due to reducer computation time) ?
Related
I like to resolve my Observable res$ before continue with log-ready. But I found no way, to achieve that. I can't move my last log-line into async context. Is there a way to solve that problem?
let o1 = Observable.create((o) => {
console.log('o1 start');
setTimeout(() => {
console.log(`o1 is running`);
o.next('o1');
}, 1500);
console.log('o1 ends');
});
let o2 = Observable.create((o) => {
console.log('o2 starts');
setTimeout(() => {
console.log(`o2 is running`);
o.next('o2');
}, 1100);
console.log('o2 ends');
});
let res$ = o1.pipe(concatMap(() => o2)).subscribe();
//ToDO resolve res$ before continue execution
console.log(`ready`);
https://stackblitz.com/edit/rxjs-b9z3wn?devtoolsheight=60
Is there a way to solve that problem?
In short: No, this is impossible.
JavaScript is a single-theaded language. Asynchronous code is all run on the same thread such that two parts of your JavaScript code will never run at the same time.
This has some advantages. For example, you rarely need to worry about mutex's, semaphores, etc. With JavaScript you can be certain that all synchronous code will run to completion before any other code is run.
There is a downside, however, it's not possible to lift an asynchronous context into a synchronous one. Any attempt to do so will never halt (the way a program fails when you write an infinite loop). This is why Node.js was (still is for older code) infamous for callback hell. It relies heavily on shoving callbacks onto the event loop since it is never allowed to wait. Promises and Observables are two ways to manage this complexity.
JavaScript is single threaded. So if you wait for 10 seconds, the whole program will hang for 10 seconds and do nothing. If you try to wait for something else to happen, the single thread will be busy waiting for forever and therefore be too busy to go do the thing it's waiting for.
You can convert an Observable to a Promise and await it afterwards. Try this:
await o1.pipe(concatMap(() => o2)).toPromise()
In the example on dart.dev the Future prints the message after the main function was done.
Why might the Future work after the main function was done? At first glance, after the completion of the main function, the entire work of the program is expected to be completed (and the Future must be cancelled).
The example code:
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info from another service or database.
return Future.delayed(Duration(seconds: 2), () => print('Large Latte'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
The program prints
Fetching user order...
Large Latte
I've expected just the following
Fetching user order...
This has to do with the nature of futures and asynchronous programming. Behind the scenes, Dart manages something called the asynchronous queue. When you initiate a future (either manually like you did with Future.delayed or implicitly by calling a method marked async, that function's execution goes into the queue whenever its execution gets deferred. Every cycle when Dart's main thread is idle, it checks the futures in the queue to see if any of them are no longer blocked, and if so, it resumes their execution.
A Dart program will not terminate while futures are in the queue. It will wait for all of them to either complete or error out.
The first question is when the event loop starts ?
I read in a site that it's start after the main method
but why when we try something like this
main()async {
Future(()=>print('future1'));
await Future(()=>print('future2'));
print('end of main');
}
//the output is :
//future1
//future2
//end of main
in this example the event loop start when we use the await keyword and
after the event loop reaches the future2 it's paused ?
or i am wrong :(
The second question is how the events is added to event queue
if it's FIFO why in this example the future 2 is completed before
future 1
main(){
Future.delayed(Duration(seconds:5) , ()=>print('future1'));
Future.delayed(Duration(seconds:2) , ()=>print('future2'));
}
The event loop run when there is nothing else running (e.g. main method is done, you are waiting for some future to complete).
Your example makes sense because the first line puts an event on event queue so now the first item in the queue is "print('future1')". In the next line, you are putting another event on the queue which calls "print('future2')" and now you await for this event to be done.
Since your main method is not waiting for something then the event loop is going to be executed. Since the first event on the queue was "print('future1')" then this is going to be executed first. But since the main method is still waiting for the future "print('future2')" to be complete then the event loop takes another event to be executed which are going to be "print('future2')".
Since this event was the one the main method was waiting for (and there is no more event on the event queue) then main() are going to run the last call "print('end of main')".
In your next example, you think that Future and Future.delayed are the same which it is not. With Future.delayed there are not going any event in the event queue before. Instead, there are running a thread outside the VM which knows when the next timer should trigger which ends up putting an event on the queue. So the event is only being put on the event queue when the timer has been expired (and therefore, the future2 are going to be executed first).
I have a following Saga:
function* interiorFileSaga() {
yield [
takeLatest(wizardActionTypes.UPLOAD_INTERIOR_FILE, handleInteriorFileUpload),
takeLatest(wizardActionTypes.INTERIOR_FILE_PROCESSING, handleInteriorFileProcessing),
]
}
that is responsible for uploading and processing file. Inside the handleInteriorFileProcessing I'm having a while loop, that is checking file processing progress etc. I want to cancel the forked process, when we dispatch a certain action - when user changes page, on component unmount I want to stop the process. I know that cancel effect is taking a process as an argument. Is there a way I can cancel all child processes? What would be the correct syntax here, to cancel handleInteriorFileProcessing process on let's say CANCEL_BACKGROUND_JOB. I'm a Saga beginner and can't find a way to cancel a process in saga that is responsible for forking a few tasks like here.
Best wishes.
Simplest would probably be to do
takeLatest([wizardActionTypes.INTERIOR_FILE_PROCESSING, CANCEL_BACKGROUND_JOB], handleInteriorFileProcessing)
and the check inside your function what actually happened.
This works because takeLatest cancels running tasks from that statement.
An alternative way to cancel things is to use the race effect, which cancels all 'losers'.
I have been trying in various ways to make my program sleep for 10 seconds before running the next line of code.
this.SetContentView (Resource_Layout.Main)
let timer = new System.Timers.Timer(10000.0)
async{do timer.Start()}
this.SetContentView (Resource_Layout.next)
I can't get any solution to work.
If you want to use async rather than the more direct way (of creating a timer and setting the content view in the event handler of the timer), then you need something like this:
this.SetContentView (Resource_Layout.Main)
async{
do! Async.Sleep(10000.0)
this.SetContentView (Resource_Layout.next) }
|> Async.StartImmediate
The key points:
Using do! Async.Sleep you can block the execution of asynchronous computation
By moving the SetContentView call inside the async, it will happen after the sleep
Using Async.StartImmediate, you start the workflow - and the sleeping ensures that the rest of the computation runs in the same threading context (meaning that it will run on the UI thread and the code will be able to access UI elements).