setTimeout / Promise.resolve: Macrotask vs Microtask - javascript

I've been introduced to the concepts of Microtasks and Macrotasks for a while now, and from everything I've read, I always thought setTimeout to be considered to create a macrotask and Promise.resolve() (or process.nextTick on NodeJS) to create microtasks.
(Yes, I'm aware that different Promise libraries like Q and Bluebird have different schedulers implementations, but here I'm referring to the native Promises on each platform)
With this in mind I'm unable to explain the following sequence of events on NodeJS (results on Chrome are different from NodeJS (both v8 LTS and v10) and match with my understanding on this subject).
for (let i = 0; i < 2; i++) {
setTimeout(() => {
console.log("Timeout ", i);
Promise.resolve().then(() => {
console.log("Promise 1 ", i);
}).then(() => {
console.log("Promise 2 ", i);
});
})
}
So, the results I have on Chrome (and that are consistent with my understanding of Micro/Macro tasks and how Promise.resolve and setTimeout behave) are:
Timeout 0
Promise 1 0
Promise 2 0
Timeout 1
Promise 1 1
Promise 2 1
The same code executed on NodeJS outputs:
Timeout 0
Timeout 1
Promise 1 0
Promise 2 0
Promise 1 1
Promise 2 1
I'm looking for a way to have the same results on NodeJS that I have on Chrome. I've also tested with process.nextTick instead of Promise.resolve() but the results are the same.
Can anyone point me into the right direction?

This was recognized by the NodeJs team as a bug, more details here: https://github.com/nodejs/node/issues/22257
Meantime it was already fixed and released has part of Node v11.
Best,
José

You can't control how different architectures queue the promises and timeouts.
Excellent Read Here: https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
If you want the same results you are going to have to chain promises.
let chain = Promise.resolve(null)
for (let i = 0; i < 2; i++) {
console.log("Chaining ", i);
chain = chain.then(() => Promise.resolve()
.then(() => {
setTimeout(() => {
console.log("Timeout ", i);
Promise.resolve()
.then(() => {
console.log("Promise 1 ", i);
})
.then(() => {
console.log("Promise 2 ", i);
})
}, 0)
}))
}
chain.then(() => console.log('done'))

I am not saying I got it right, I wrote something adhoc and I'd like you to test the below:
the wrapper:
function order(){
this.tasks = [];
this.done = false;
this.currentIndex = 0;
this.ignited = false;
}
order.prototype.push = function(f){
var that = this,
args = Array.prototype.slice.call(arguments).slice(1);
if(this._currentCaller){
this.tasks.splice(
this.tasks.indexOf(this._currentCaller) + 1 + (this.currentIndex++),
0,
function(){that._currentCaller = f; f.apply(this,args);}
);
} else {
this.tasks.push(function(){that._currentCaller = f; f.apply(this,args);});
}
!this.ignited && (this.ignited = true) && this.ignite();
return this;
}
order.prototype.ignite = function(){
var that = this;
setTimeout(function(){
if(that.tasks.length){
that.tasks[0]();
that.tasks.shift();
that.repeat(function(){that.reset(); that.ignite()});
} else {
that.ignited = false;
that.reset();
}
},0);
}
order.prototype.repeat = function(f){
var that = this;
if(this.done || !this.tasks.length){
f();
} else {
setTimeout(function(){that.repeat(f);},0);
}
}
order.prototype.reset = function(){
this.currentIndex = 0;
delete this._currentCaller;
this.done = false;
}
to use:
create an instance:
var x = new order;
then modify the rest a bit:
for (let i = 0; i < 2; i++) {
x.push(function(i){
setTimeout(() => {
console.log("Timeout ", i);
x.push(function(i){
Promise.resolve().then(() => {
console.log("Promise 1 ", i);
}).then(() => {
console.log("Promise 2 ", i);
x.done = true;
})
},i);
x.done = true;
});
},i);
}
I get this:
Timeout 0
Promise 1 0
Promise 2 0
Timeout 1
Promise 1 1
Promise 2 1
You can even elaborate a bit:
for (let i = 0; i < 2; i++) {
x.push(function(i){
setTimeout(() => {
console.log("Timeout ", i);
x.push(function(i){
Promise.resolve().then(() => {
console.log("Promise 1 ", i);
}).then(() => {
console.log("Promise 2 ", i);
x.done = true;
})
},i)
.push(function(i){
Promise.resolve().then(() => {
console.log("Promise 1 ", i);
}).then(() => {
console.log("Promise 2 ", i);
x.done = true;
})
},i+0.5)
.push(function(i){
Promise.resolve().then(() => {
console.log("Promise 1 ", i);
}).then(() => {
console.log("Promise 2 ", i);
x.done = true;
})
},i+0.75);
x.done = true;
});
},i);
}
In node v6, you get:
Timeout 0
Promise 1 0
Promise 2 0
Promise 1 0.5
Promise 2 0.5
Promise 1 0.75
Promise 2 0.75
Timeout 1
Promise 1 1
Promise 2 1
Promise 1 1.5
Promise 2 1.5
Promise 1 1.75
Promise 2 1.75
Would you try this in your node version for me? In my node (6.11, I know its old) it works.
Tested on chrome, firefox, node v6.11
Note: you don't have to keep reference to 'x', this within the pushed functions refer to the order instance. You can also use Object.defineProperties to render getters/setters unconfigurable, to prevent accidental deletion of instance.ignited etc.

Related

Repeat sending the request to another server until the expected result is achieved

I'm requesting to server "S" to get some data, but this data may not be ready.
When the data is not yet ready, server S responds with {"data":null,"state": "pending"} but when the data has been prepared the response will be something like {"data": {...somedata}, "state": "done"}.
I have to repeat the request until the data is ready. What I'm doing now is something like this:
let wait = function* () {
let t = 500;
for (let j = 1; j < 10; j++) {
yield new Promise((resolve) => {
setTimeout(() => resolve(), t*=2);
});
}
}();
let result = await sendRequestToS();
status = result;
for (let i = 0; i < 4 && result.state==='pending'; i++) {
await wait.next().value;
result = await sendRequestToS();
}
As you can see, I send the request up to 4 times with a delay of 1, 2, 4 and 8 seconds.
Am I doing this the right way?
Isn't that (using setTimeout to delay between requests) a bad practice?
I'd write this as such:
function wait(ms) {
return new Promise(res => setTimeout(res, ms));
}
async function requestAndRetry() {
let retries = 10;
let timeout = 1000;
while(retries>0) {
const response = await sendRequestToS();
if (result?.state === 'done') {
return result;
}
await wait(timeout);
retries--;
timeout*=2;
}
throw new Error('Request failed after 10 retries');
}
I don't think it's a bad idea. It's called exponential back-off and you're not blocking the script engine.
Instead of using generators directly, you could simply do this using async/await and recursion. Here's an example which tries to get the response a limited number of times in order to prevent an endless recursion and with a timeout between retries:
async function wait(timeInMs) {
console.log('Waiting ...');
return new Promise((resolve => setTimeout(() => resolve(), timeInMs)));
}
async function tryRequestToS(numberOfTries, timeout) {
if (numberOfTries <= 0) {
throw new Error("could net get result");
}
const result = await sendRequestToS();
if (result && result.state === "done") {
return result;
}
await wait(timeout); // wait for the defined timeout before recurring
return tryRequestToS(numberOfTries - 1, timeout);
}
(async () => {
try {
const result = await tryRequestToS(10, 500); // max. 10 retries, 500 ms delay between retries
console.log(result);
} catch(err) {
console.log(err);
}
})();

How to call multiple functions in an array after a setTimeout between them?

Need to create a sequence of functions stored in an array. Need them to be executed every 400ms. Did it by setting 500 setTimeout(function(){})'s. Somehow learned that should do it by creating it dynamically. Have the functions ready, but missing the timeouts to make it a nice animation
Here is the part where I think I have to set the timeout's in:
function start() {
mylist.name = this.className;
mylist.identifier = 'port'+parseFloat(this.id);
mylist.number = parseFloat(this.id);
console.log(mylist.number);
for (var x in todo) {
todo[x]();
}
}
And here is the array with the functions. Thank you
ps: sorry for bad english, made in Germany :)
todo = [
function computer1() {
$('.'+mylist.name+':eq(1)').css({'background-color': 'chartreuse'});
},
function kabel1() {
$('.'+mylist.name+':eq(0)').css({'border-bottom-color': 'chartreuse'});
},
function port1() {
$('#'+mylist.identifier).css({'background-color': 'chartreuse'});
},
function port2() {
for (var x=0;x<ports;x++) {
$('#port'+(x+1)).css({'background-color': 'blue'});
if (x===(mylist.number-1)) {
$('#port'+(x+1)).css({'background-color': 'chartreuse'});
}
}
},
function kabel2() {
for (var x=0;x<ports;x++) {
$('#portugal'+(x+1)+':eq(0)').css({'border-bottom-color': 'blue'});
if (x===(mylist.number-1)) {
$('#portugal'+(x+1)+':eq(0)').css({'border-bottom-color': 'chartreuse'});
}
}
},
function device2() {
for (var x=0;x<ports;x++) {
$('#'+(x+1)+'device').css({'background-color': 'blue'});
if (x===(mylist.number-1)) {
$('#'+(x+1)+'device').css({'background-color': 'chartreuse'});
}
}
},
function device3() {
for (var x=0;x<ports;x++) {
if (document.getElementById('info').textContent==document.getElementById((x+1)+'device').className) {
var hammer = document.getElementById((x+1)+'device').getAttribute('class');
$('.'+hammer+':eq(1)').css({'background-color': 'red'});
}
}
},
function mehr() {
}]
Something like that?
let startTime = new Date().getTime();
let functions = [
()=>console.log("f0: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f1: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f2: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f3: " + (new Date().getTime() - startTime) + " ms passed"),
()=>console.log("f4: " + (new Date().getTime() - startTime) + " ms passed"),
]
let i = 0;
setInterval(() => {
functions[i]();
i++;
if(i > functions.length - 1) i = 0;
}, 400);
If it worked always smash the accept button ;)
I'm not in favor of changing the array and modifying synchronous function. Thus changing for loop.
// Created variable using let to over come closure
for (let x in todo) {
setTimeout( ()=> {
todo[x]();
}, (x+1)*400);
}
You can avoid creating unnecessary closures or computing specific timeouts
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const funcs =
[ () => console.log (1)
, () => console.log (2)
, () => console.log (3)
]
const forEachDelay = async (ms, [ f = () => {}, ...fs ]) =>
fs.length === 0
? f ()
: delay (ms, f ()) .then (() => forEachDelay (ms, fs))
forEachDelay (1000, funcs)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
An advantage of using async is we get a Promise back and we know when the entire sequence is done –
forEachDelay (1000, funcs) .then (() => console.log ('done'), console.error)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
// done
Promise is an effective data type because it allows you to combine smaller steps into one big-picture step.
Above we write forEachDelay using a functional expression, but we could also use an imperative-style for loop combined with the powerful await syntax –
const delay = (ms, x) =>
new Promise (r => setTimeout (r, ms, x))
const funcs =
[ () => console.log (1)
, () => console.log (2)
, () => console.log (3)
]
const forEachDelay = async (ms, fs = []) =>
{ for (const f of fs)
await delay (ms, f ())
}
forEachDelay (1000, funcs) .then (() => console.log ('done'), console.error)
// 1
// (1000 ms later...) 2
// (1000 ms later...) 3
// done

The differences of "setInterval" between chrome and other browsers, Why?

CODE 1
console.log('start');
const interval = setInterval(() => {
console.log('setInterval');
}, 0);
setTimeout(() => {
console.log('setTimeout 1');
Promise.resolve()
.then(() => {
console.log('promise 3');
})
.then(() => {
console.log('promise 4');
})
.then(() => {
setTimeout(() => {
console.log('setTimeout 2');
Promise.resolve()
.then(() => {
console.log('promise 5');
})
.then(() => {
console.log('promise 6');
})
.then(() => {
clearInterval(interval);
});
}, 0);
});
}, 0);
Promise.resolve()
.then(() => {
console.log('promise 1');
})
.then(() => {
console.log('promise 2');
});
The result of this code on Windows Edge/Mac Safari/Opera browsers is as follow:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setTimeout 2
promise 5
promise 6
But when ran on chrome on Windows or Mac, the result have two cases.
Sometimes the result is same as above.
Sometimes the result outputs two setInterval as follow:
start
promise 1
promise 2
setInterval
setTimeout 1
promise 3
promise 4
setInterval
setInterval
setTimeout 2
promise 5
promise 6
I'm confused about the result.
CODE 2
function expendTime(k){
console.log((new Date()).getTime());
while(k < 10000){
for(var j = 2; j < k; j++){
if(k%j == 0){
break;
}
if(j == k-1){
console.log(k)
}
}
k++;
}
console.log((new Date()).getTime());
}
var t = setInterval(expendTime,15,3);
setTimeout(function() {
clearInterval(t);
},30);
.as-console-wrapper {
max-height: 100% !important;
}
The result of this code when ran on chrome and others browsers is different.
I consider the result on other browsers is correct, since it is coincides with my common sense.
Yes chrome's* implementation of setInterval does self-correct itself so its calls do fire at exact intervals, removing any drift.
*and maybe Edge's one ?
Here is an example code which does implement such a drift-correcting setInterval method, ran in chrome, you can see it has the same effect than the builtin one, while other implementations and recursive setTimeout will keep growing the drift.
// drift-correcting setInterval
// returns an object which "_id" property is the inner timeout id, so it can be canceled by clearInterval
function driftCorrectingInterval(cb, delay) {
var begin = performance.now(), // what time is it?
result = {
_id: setTimeout(inner, delay)
},
passed = true; // a flag to avoid try-catch the callback
return result;
function inner() {
if (!passed) return; // 'cb' thrown
passed = false; // set up the callback trap
var now = performance.now(),
drift = (now - begin) - delay;
begin += delay; // start a new interval
result._id = setTimeout(inner, delay - drift);
// call it at the end so we can cancel inside the callback
cb();
passed = true; // didn't throw we can continue
}
}
// snippet-only tests
function test(ms) {
function setTimeoutLoop(cb, ms) {
function loop() {
cb();
setTimeout(loop, ms);
}
setTimeout(loop, ms);
}
var now = performance.now(),
built_in_prev = now,
timeout_prev = now,
sCI_prev = now,
built_in_elem = document.querySelector('#test_' + ms + ' .delay.built_in'),
timeout_elem = document.querySelector('#test_' + ms + ' .delay.timeout'),
sCI_elem = document.querySelector('#test_' + ms + ' .delay.sCI');
setInterval(() => {
var now = performance.now(),
delay = (now - built_in_prev) - ms;
built_in_prev += ms;
built_in_elem.textContent = Math.round(delay);
}, ms);
setTimeoutLoop(() => {
var now = performance.now(),
delay = (now - timeout_prev) - ms;
timeout_prev += ms;
timeout_elem.textContent = Math.round(delay);
}, ms);
driftCorrectingInterval(() => {
var now = performance.now(),
delay = (now - sCI_prev) - ms;
sCI_prev += ms;
sCI_elem.textContent = Math.round(delay);
}, ms);
}
test(1000);
[id^='test'] {
border: 1px solid;
padding: 0 12px
}
<div id="test_1000">
<p>built in setInterval drift: <span class="delay built_in">0</span>ms</p>
<p>built in setTimeout loop drift: <span class="delay timeout">0</span>ms</p>
<p>driftCorrectingInterval drift: <span class="delay sCI">0</span>ms</p>
</div>
Note that current specs read as Firefox's implementation, and as what you understood, i.e a recursive setTimeout, without any consideration for drift.
Indeed the timer initialization steps requires that when the repeat flag is set to true, the same arguments should be passed to the next timer initialization steps method.
But an open issue discusses this very problem and might lead to a revision of the specs so that UAs are encouraged to apply such drift-correction when possible.

Recursion with Promises means registered callbacks get called many times

So my thinking was right, when recursing with promises, we end up calling all chained callbacks for however many times we recurse, for example
function p() {
return new Promise(function (r) {
process.nextTick(r);
})
}
function recurse(count) {
return p().then(function () {
if (count < 10) {
console.log('count => ', count);
return recurse(++count);
}
}).then(function(){
console.log('a');
return 5;
});
}
recurse(1).then(function () {
console.log('done');
});
If you run the above, we get:
count => 1
count => 2
count => 3
count => 4
count => 5
count => 6
count => 7
count => 8
count => 9
a
a
a
a
a
a
a
a
a
a
done
Is there a way to register this callback with console.log('a') just once instead of registering it 10 times?
I don't think I want/need this function to be called 10 times, and would like to find a way to have it called just once.
I am actually just as interested in a similar solution for Observables, specifically RxJS5 Observables.
I guess the only solution is to nest the remainder of your code inside the first promise callback like so:
function p() {
return new Promise(function (r) {
process.nextTick(r);
})
}
function recurse(count) {
return p().then(function () {
if (count < 10) {
return recurse(++count);
} else {
// all your remaining code would have to go here
console.log('a');
return 5; // or return someOtherPromise() or anything
}
});
}
recurse(1).then(function () {
console.log('done');
});
If the recursion is synchronous, you can simply recurse within the .then's function
new Promise(res => {
res(); // dummy initial promise
}).then(() => {
function recurse(x) { // recursion inside then
console.log('x', x);
if (x < 10) return recurse(++x);
return x;
}
return recurse(1); // begin recursion
}).then(y => { // this only fires once recursion above is resolved
console.log('y', y);
return 5;
}).then(z => console.log('z', z));
// x 1
// ... (synchronous)
// x 10
// y 10 (value passed from resolving inner promise)
// z 5 (value returned from previous then)
Or if our recursive function is asynchronous, we can have it return a promise too, so you end up with a recursion which looks like this
function doWork() {
function addOne(x) {
return new Promise((res, rej) => {
// async bit here
res(x + 1);
});
}
function recurse(x) {
if (x < 10) return addOne(x).then(recurse);
return x;
}
return recurse(1);
}
And in a promise chain, would look like this
new Promise(res => {
res(); // dummy initial promise
}).then(() => {
return doWork();
}).then(y => {
console.log(y);
});

Bluebird promise: why it doesn't timeout?

So, I'm trying to model some long computation. for this purpose I'm computing the fibonacci number. In case when computation takes to much time I need to reject it.
The question: why TimeoutErrror handler doesn't work? How to fix the code?
const expect = require('chai').expect
const Promise = require('bluebird')
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return profib(n, cur, strAdd(cur, prev));
}
})
}
const TIMEOUT = 10000
const N = 20000
describe('recursion', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
let prom = profib(N).timeout(1000)
.catch(Promise.TimeoutError, function(e) {
console.log('timeout', e)
return '-1'
})
return prom.then((num) => {
expect(num).equal('-1')
})
})
})
const strAdd = function(lnum, rnum) {
lnum = lnum.split('').reverse();
rnum = rnum.split('').reverse();
var len = Math.max(lnum.length, rnum.length),
acc = 0;
res = [];
for(var i = 0; i < len; i++) {
var subres = Number(lnum[i] || 0) + Number(rnum[i] || 0) + acc;
acc = ~~(subres / 10); // integer division
res.push(subres % 10);
}
if (acc !== 0) {
res.push(acc);
}
return res.reverse().join('');
};
Some info about environment:
➜ node -v
v6.3.1
➜ npm list --depth=0
├── bluebird#3.4.6
├── chai#3.5.0
└── mocha#3.2.0
If I'm reading your code correctly profib does not exit until it's finished.
Timeouts are not interrupts. They are just events added to the list of events for the browser/node to run. The browser/node runs the next event when the code for the current event finishes.
Example:
setTimeout(function() {
console.log("timeout");
}, 1);
for(var i = 0; i < 100000; ++i) {
console.log(i);
}
Even though the timeout is set for 1 millisecond it doesn't appear until after the loop finishes (Which takes about 5 seconds on my machine)
You can see the same problem with a simple forever loop
const TIMEOUT = 10000
describe('forever', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
while(true) { } // loop forever
})
})
Run in with your environment and you'll see it never times out. JavaScript does not support interrupts, it only supports events.
As for fixing the code you need to insert a call to setTimeout. For example, let's change forever loop so it exits (and therefore allows other events)
const TIMEOUT = 100
function alongtime(n) {
return new Promise(function(resolve, reject) {
function loopTillDone() {
if (n) {
--n;
setTimeout(loopTillDone);
} else {
resolve();
}
}
loopTillDone();
});
}
describe('forever', function() {
it.only('cancelation', function(done) {
this.timeout(2 * TIMEOUT)
alongtime(100000000).then(done);
})
})
Unfortunately using setTimeout is really a slow operation and arguably shouldn't be used in a function like profib. I don't really know what to suggest.
The problem appears because promises work in a "greedy" manner(it's my own explanation). For this reason function profib doesn't release event loop. To fix this issue I need to release event loop. The easiest way to do that with Promise.delay():
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return Promise.delay(0).then(() => profib(n, cur, strAdd(cur, prev));
}
})
}
gman has already explained why your idea doesn't work. The simple and efficient solution would be to add a condition in your loop that checks the time and breaks, like thus :
var deadline = Date.now() + TIMEOUT
function profib(n, prev = '0', cur = '1') {
if (Date.now() >= deadline) throw new Error("timed out")
// your regular fib recursion here
}
Calling profib will either eventually return the result, or throw an error. However, it will block any other JavaScript from running while doing the calculation. Asynchronous execution isn't the solution here. Or at least, not all of it. What you need for such CPU-intensive tasks is a WebWorker to run it in another JavaScript context. Then you can wrap your WebWorker's communication channel in a Promise to get the API you envisioned originally.

Categories

Resources