Goal is to push sampled data, as an object, onto an array, at a periodic interval and wait to log the new array out to the console once it is finalized.
I'm new to JS, so take it easy ;). I am likely making this more complicated than it needs to be. Thought it would be as simple as a setTimeout() in a for loop.
I have been able to generate the array two different ways, using IIFE with a setTimeout() also the setInterval() below. Not sure how to get the async await function working with an array push() method querying length. Maybe this is not a good approach?
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function arrayGenerator(tag){
return sampleArr.push(new Sample(tag));
};
function setIntSample(callback, delay, iterations) {
var i = 0;
var intervalID = setInterval(function () {
callback(i);
if (++i === iterations) {
clearInterval(intervalID);
}
}, delay);
};
Above seems to work console.log()-ing the array as it is generated in the arrayGenerator() function. Below, no dice
function resolveAfterArrGeneration(){
return new Promise(resolve => {
arrLength = setIntSample(i => {arrayGenerator(i)}, 3000, 5)
if (arrLength === 5) {resolve();}
});
}
async function ans() {
var answer = await resolveAfterArrGeneration();
console.log(sampleArr);
}
ans();
The basic idea is to return a promise and resolve the promise when the setInterval has run enough iterations. You can do that in a single function with something like this (with extra console.logs to show the process):
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
function makeSamples(iterations, delay){
let samples = [], i = 0;
return new Promise(resolve => {
let intervalID = setInterval(function () {
console.log("pushing new sample")
samples.push(new Sample('tag: ' + i));
if (++i === iterations) {
console.log("finished resolving")
clearInterval(intervalID);
resolve(samples)
}
}, delay);
})
}
makeSamples(5, 1000).then(console.log)
I would isolate the delay part (the asynchronous) part and create a separate, generic function delay() for that. All the rest becomes simple then, using an async function and for loop:
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
class Sample {
constructor(tag, timeStamp) {
this.tag = tag;
this.timeStamp = Date.now();
}
}
async function setIntSample(callback, ms, iterations) {
const arr = [];
for (let i = 0; i < iterations; i++) {
if (i) await delay(ms); // don't delay first time
arr.push(callback(i));
}
return arr;
}
const newSample = (tag) => new Sample(tag)
console.log("wait for it....");
setIntSample(newSample, 1000, 5).then(console.log);
Another way I just got working with a generator function
function* simpleGenerator(){
var index = 0;
while (true)
yield {tag: index++, time: Date.now()}
}
var gen = simpleGenerator();
..with the corresponding push
arr.push(gen.next().value);
Related
I'm a complete beginner at JavaScript. I just want to call the function called seconONe() just after the function firstOne() completes its execution. by saying this, I mean the function two will call when the value of that p1 is 4 ( in this case ); I can achieve it by calling a setTimeout() function. but what if I don't know how many does it take to execute { the first one() }?
// getting DOM element
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1.innerHTML = i;
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
A possible solution is to work with promises. More info about promises here.
Working example
var p1 = 1;
var p2 = 2;
var p3 = 3;
const firstPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p1 = i;
}, i * 1000);
}
resolve()
});
const secondPromise = new Promise((resolve, reject) => {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2 = i;
}, i * 1000);
}
resolve()
});
//run first promise
console.log("First promise called")
firstPromise
.then((response) => {
console.log("First promise done")
//run second promise after first promise succeed
console.log("Second promise called")
secondPromise
.then((response) => console.log("Second promise done"))
})
your question is not childish at all. What you will need to understand are callbacks and promise handlers. This just tells JavaScript to wait till a task has been completed in order to execute the next task.
firstOne().then(() => secondOne())
put if condition in your firstOne function.
const p1 = document.getElementById(`one`);
const p2 = document.getElementById(`two`);
const p3 = document.getElementById(`three`);
// first function
function firstOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
if(i == 4){
seconOne();
}else{
p1.innerHTML = i;
}
}, i * 1000);
}
}
// second function
function seconOne() {
for (let i = 0; i < 5; i++) {
setTimeout(() => {
p2.innerHTML = i;
}, i * 1000);
}
}
Just to build on the other answers that have suggested using a Promise, here's a more generalised solution that also uses async/await.
(In summary: call a function with a count, and an element. That function will return a promise that "at some point" work will be completed. An inner function loops updating the element content until that count has been reached, at which point the promise resolves, and the next thing can start).
// Cache the elements
const p1 = document.querySelector('#one');
const p2 = document.querySelector('#two');
const p3 = document.querySelector('#three');
// `timer` accepts a count, and the element
// to apply the count to
function timer(count, el) {
// Return a promise that basically says:
// once I'm done doing this work, resolve,
// and then the event queue can
// get on with the next thing
return new Promise(resolve => {
// So we create a loop that logs the numbers
// in our element up to the count we specified.
// and when that number is reached, resolve the promise
function loop(n = 0) {
// If our current `n` value is <= count
if (n <= count) {
// Set the content of the element
el.textContent = n;
// Call `loop` again after a second
// with an incremented `n` value
setTimeout(loop, 1000, ++n);
// Otherwise resolve the promise
} else {
resolve();
}
}
loop();
});
}
// And now we just await each resolved promise
async function main() {
await timer(4, p1);
await timer(7, p2);
await timer(20, p3);
console.log('Done!');
}
main();
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
Additional documentation
querySelector
const sleep = (time) => {
return new Promise((resolve) => setTimeout(resolve, time))
}
const doSomething = async () => {
for (let i = 0; i < 100; i++) {
await sleep(1000)
console.log(i)
}
}
doSomething()
Why is this promise working as expected? I'm learning about Promise and stuck on this. The function passed into the Promise constructor does not have reject() and yet it is working.
The snippet is from this article: https://medium.com/javascript-in-plain-english/javascript-slow-down-for-loop-9d1caaeeeeed
I'm also struggling to understand the explanation in the article about the differences between the setTimeout approach (that does not work) vs. setTimeout in promise approach (that works).
Thanks for any pointer on this topic.
Arguments are optional in JavaScript so the reject argument is just ignored.
As for why the first example doesn't work is that setTimeout registers an event for the event loop to call in the future. Things in JavaScript do not block. Internally await rewrites the code so that it does not block, but the code still reads as synchronous, but it is not.
There are plenty of great examples out there explaining the JavaScript event loop, and I'd highly suggest reading up on it.
The Promise is not resolved until the resolve ("res") function is called. The function that gets passed to setTimeout does not run until the timeout finishes.
Here is an alternative example, where the resolve function returns the count that gets passed-in.
const main = async () => {
const done = await doSomething(10, 1000, i => console.log(i));
console.log('Done!');
};
const doSomething = async (times, duration, callback) => {
for (let counter = 0; counter < times; counter++) {
const count = await sleep(counter, duration);
callback(count);
}
return true;
};
const sleep = (count, duration) => {
return new Promise((res) => setTimeout((n) => res(n), duration, count));
};
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
Here is a less-coupled version, with some default parameters.
const DEBUG = true;
const main = async () => {
const worker = (n, m) => console.log(`Step ${n + 1}/${m}`);
await doSomething(worker);
};
const doSomething = async (callback, duration = 1000, times = 10) => {
if (DEBUG) console.log('Start!');
for (let counter = 0; counter < times; counter++) {
await sleep(duration, counter, times);
callback(counter, times);
}
if (DEBUG) console.log('Done!');
return true;
};
const sleep = (duration, count = 1, times = 1) => {
return new Promise(res => setTimeout(res, duration));
};
main();
.as-console-wrapper { top: 0; max-height: 100% !important; }
I've written a short script using Async/Await that prints out letters one by one after short intervals. Based on what I understood to be happening, I tried rewriting the code in several ways expecting the same result, but I have been unable to make any of these alternatives work. In particular, I thought it would be straightforward to change where in the code the console.log() happens.
Here's the original working code:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
// Async/Await with a For loop calling setTimeoutPromise()
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
sendMessage();
}
welcomeMessage();
Then I tried to make a few modifications, none of which worked.
Mdofication #1: In this version, I thought I could just call and run the code in the sendMessage() function directly without needing to call it later. However, nothing happened after this modification:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
}
welcomeMessage();
Modification #2: I reverted the code and then tried to move the console.log() function into the setTimeout() function thinking this would be called on every loop. Both with empty ()'s and with (resolve) being passed into setTimeout(), it only printed the first letter. With (resolve, num) it says undefined:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => {
console.log(message[num]);
resolve;
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Modification #3: Finally, I tried to define a function in advance to be passed into setTimeout() which would be used to handle "resolve" and console.log(). I tried a few variations and again didn't seem to be progressing through the loop as console.log() was only called once.
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func;
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
It appears to me, that you've started working with asynchrony before you got a deep understanding of how synchronous JavaScript works. Asynchrony is hard enough on its own too, so combined to that, it made you completely confused.
Let me explain what's going on and what's wrong in your snippets.
Let's start with the working one.
That code:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
...creates a function named setTimeoutPromise, that:
takes an index (number) as its argument
returns a promise that:
after timer[num] milliseconds
resolves to undefined (setTimeout doesn't pass anything to its callback by default; in this case the callback is the resolve function)
The next part:
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...defines an async function named sendMessage, that:
iterates over message, for each character:
calls setTimeoutPromise and awaits the promise it returns
after waiting, logs the current character to the console
Finally,
sendMessage();
...calls sendMessage, and therefore initiates the typing.
Now, let's move on to the next snippet.
This code:
async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
};
...creates an async function, but it doesn't call or assign it to any variable: simply discards it.
To fix this snippet, call the function immediately by putting () after it!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// Promisify setTimeout() and feed in counter from sendMessage()
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(resolve, timer[num]);
})
};
(async () => { //No name and removed call to sendMessage() later in code
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
console.log(message[count]);
};
})(); //The () at the end calls it
}
welcomeMessage();
Problematic snippet #2
There are 2 problems with this:
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout((resolve) => { //Problem 1
console.log(message[num]);
resolve; //Problem 2
}, timer[num]);
})
};
You try to take an argument named resolve from setTimeout, but as I mentioned above, it doesn't pass any.
To solve it, remove resolve from setTimeout((resolve) => {! We already have the resolve function from the above line, because of lexical scope.
You don't call resolve, that keeps the awaiting code hanging after the first letter (the promise never gets resolved).
To fix it, put () after resolve!
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(() => {
console.log(message[num]);
resolve();
}, timer[num]);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage();
}
welcomeMessage();
Problematic snippet #3
There are 2 problems in this code as well:
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func; //Problem 1
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction(resolve, num), timer[num]); //Problem 2
})
};
The same as above; newFunction doesn't call resolve (named fn).
Try to not forget the () when you intend to call a function
That's the opposite of Problem 1. You immediately call newFunction (due to the parentheses after it: (resolve, num)), and pass its return value (undefined) to the setTimeout. Without Problem 1, this would result in immediately logging all letters.
In this case, let setTimeout to call that function internally by removing (resolve, num) after it. To pass parameters to it, setTimeout accepts additional arguments, that it will hand over to its callback (in this case newFunction).
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
const sendMessage = async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
};
sendMessage()
}
welcomeMessage();
All together...
It is possible to combine these fixes, to get something like:
const welcomeMessage = () => {
const message = 'hello'
const timer = [200,400,200,400,200,400];
// New function to handle resolve and the counter
function newFunction(func, num) {
console.log(message[num]);
func();
}
const setTimeoutPromise = num => {
return new Promise(resolve => {
setTimeout(newFunction, timer[num], resolve, num);
})
};
(async () => {
for (count = 0; count < message.length; count++) {
await setTimeoutPromise(count);
};
})();
}
welcomeMessage();
Conclusion
Use parentheses (()) to call a function, but avoid them to use the function as an object: pass or assign it to something, get or set its properties, etc.
My goal is to make a video playlist generator that runs at specific times, along with a running clock and many other things that run periodically, but I'm stuck at the part where it generates the playlist at the times I define.
I'm using FileHound to walk a folder, which works whenever I call the find() method, but as it's Promise driven, its results aren't readily available to be used by the following console.log(), for example. Same thing happens with MediaInfo(), but this time I was able to work around it with async/await, which as far as I know is the actual way to use a Promise based function.
Now, as far as I can understand, the .each() and .then() method chaining are the ways to use the results from a Promise driven function, but that would quickly result in code repetition for every time I want to do the same thing in different places.
All being said, I think I'm off my track by very far, and despite my efforts I can't seem to find a clear way to achieve what I want, so I'm asking for help. This is the code I have so far, and I'm using the following npm packages:
node-mediainfo
filehound
moment
const MediaInfo = require("node-mediainfo");
const Path = require("path");
const FileHound = require("filehound");
const Moment = require("moment");
const START_TIME = Moment();
const END_TIME = null;
const VIDEO_PATH = "videos";
let files = FileHound.create()
.path(VIDEO_PATH)
.depth(0);
let playlist = [];
let start_time = START_TIME.add(10, "seconds");
const ArrayShuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
const MakePlaylist = async (file) => {
let fileinfo = await MediaInfo(Path.resolve(file));
let duration = fileinfo.media.track[0].Duration;
let title = fileinfo.media.track[0].Title || Path.basename(file, Path.extname(file));
let artist = fileinfo.media.track[0].Performer || null;
playlist.push({
path : Path.resolve(file),
title : title,
artist : artist,
duration: duration
});
};
console.log(start_time.toLocaleString());
/* Main Loop */
setInterval(() => {
if (Moment().isSame(start_time)) {
console.log("First Run");
files.find().each(MakePlaylist).then(ArrayShuffle);
console.log(playlist);
}
if (Moment().isSame(Moment(start_time).add(30, "seconds"))) {
console.log("Second Run");
playlist = [];
files.find().each(MakePlaylist).then(ArrayShuffle);
console.log(playlist);
}
}, 1);
I would be tempted to put your logic into an async function, making use of await. and then simply recall your function later, instead of trying to do it in setTimeout
Assuming files.find() returns a promise....
const GeneratePlaylist = async () => {
var files = await files.find();
var playlist = [];
for(var i=0;i<files.length;i++){
playlist.push(await MakePlaylist(files[i]));
}
ArrayShuffle(playlist);
return playlist;
}
Then you can use that from another async function, and you can recall it 1ms later with setTimeout (Note, you should make MakePlaylist return the new item, not push to a global array):
const doMyThing = async (){
if (Moment().isSame(start_time)) {
console.log("First Run");
playlist = await GeneratePlaylist();
console.log(playlist);
}
if (Moment().isSame(Moment(start_time).add(30, "seconds"))) {
console.log("Second Run");
playlist = [];
playlist = await GeneratePlaylist();
console.log(playlist);
}
setTimeout(doMyThing,1);
}
Below is a working example, where I've just faked up some of your functionality using Promises to simulate asynchronous work like finding files and loading the media info:
const ArrayShuffle = (array) => {
for (let i = array.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
};
const MakePlaylist = async (file) => {
let fileinfo = await new Promise(resolve => setTimeout(() => resolve({media:{track:[{Duration:1,Title:"Title",Performer:"Foo"}]}}),1000));
let duration = fileinfo.media.track[0].Duration;
let title = fileinfo.media.track[0].Title || Path.basename(file, Path.extname(file));
let artist = fileinfo.media.track[0].Performer || null;
return{
path : file,
title : title,
artist : artist,
duration: duration
};
};
const findFileMockup = () => {
return new Promise(resolve => setTimeout(() => {
resolve(["file1.txt","file2.txt","file3.txt"]);
},500));
}
const GeneratePlaylist = async () => {
var files = await findFileMockup(); // await files.find() in your code
var playlist = [];
for(var i=0;i<files.length;i++){
playlist.push(await MakePlaylist(files[i]));
}
ArrayShuffle(playlist);
return playlist;
}
const test = async () => {
var playlist = await GeneratePlaylist();
console.log(playlist);
}
test();
I am writing a NodeJS app that will load data from a database, parse it, and then save the parsed result to a different table in the database. Here is what I currently have:
parse(index, from, to) {
var collection = this.getCollectionName();
var interval = global.Settings.Parser.ParseInterval;
var promises = [];
console.log('%d - %d', from, from + interval);
for(from; from < to; from += interval) {
promises.push(new Promise((resolve, reject) => {
var scoped = from;
this.data.query(collection, {[index]: { $gte: from, $lte: from + interval}, (result) => {
for(var i = 0; i < result.length; i++)
this.sendToBuilder(result[i]);
resolve();
});
}));
}
promises.reduce((promise) => {
Promise.resolve()
});
}
The code seems to do what it should, but since the database query is asynchronous, it seems that out-of-order is a common occurrence. I do not want this to happen. I want each query and promise to execute sequentially to maintain the order of data.
I am trying the array.reduce() method to try and chain each promise to execute sequentially, but due to the nature of Promises it simply starts the promise and continues on, making them all fire simultaneously.
How can I ensure that it will execute sequentially? I don't mind delays between each promise as long as it doesn't block the actual thread.
Here's a working example in the spirit of your code using async/await:
function getData(reqId, collection, index, gte, lte) {
return new Promise((resolve, reject) => {
const delay = Math.floor(Math.random() * 2000) + 1;
console.log(`[getData ${reqId}] delay: ${delay}`);
const params = { collection, [index]: { $gte: gte, $lte: lte } };
const results = [0, 1, 2].map(r => `result ${reqId}.${r}`);
setTimeout(() => {
console.log(`[getData ${reqId}] this.data.query(${JSON.stringify(params)})`);
resolve(results);
}, delay);
});
}
async function parse(index, from, to) {
const collection = 'My Collection';
const interval = 10;
console.log(`Processing ${from} to ${to} by ${interval}:`);
for (from; from <= to; from += interval) {
const reqId = from;
console.log(`[parse] BEGIN ${reqId}`);
const results = await getData(reqId, collection, index, from, from + interval);
results.forEach(result => {
console.log(`[parse - awaited ${reqId}] this.sendToBuilder(${result})`);
});
console.log(`[parse] END ${reqId}`);
}
}
parse('idx', 200, 250);