I have a sudoku board implemented as an HTML table and a button that when clicked solves the sudoku board using a recursive backtracking algorithm in javascript. Now I want to make it so that you can see the adjustments being made by not altering the HTML immediately in my recursive function. I tried making the function async and then calling this function
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
like so
for (let choice = 1; choice <= 9; choice++) {
this.boardArray[row][col] = choice;
// put delay here
await sleep(250);
currEntry.textContent = choice;
if (this.choiceOkay(row, col)) {
const solved = this.solveBoard(nextRow, nextCol);
if (solved) {
return true;
}
}
This does give me the desired behavior initially but only for a portion of the board and then it seems to just stop. I removed the calls to the sleep function and tried it with the only alteration being "async" in front of the function declaration and it still only did a portion of the board but this time all at once without the visual delay. I'm wondering why making this function async causes this logic error?
Thanks in advance!
Also, this is my first question on Stack Overflow so let me know if I need to be more specific or anything along those lines.
The async keyword in front of a function basically says:
This function will return a Promise
Your sleep function already returns a Promise, so writing the keyword async is useless.
What matters is the await keyword which basically says:
On my right, there may be an async function (idem a Promise). Wait for my return before continuing
If you write async function sleep, but omit the await keyword when you call sleep, you throw the function call in the nature and never waits for its return to come back (hence your code running "without delay")
If you want delay, it is better to let your algorithm be as is, and let the caller of your algorithm tell your algorithm to continue or not.
You can for that matter use generators.
(An other possibility could be trampolining).
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield; // gives control back to delayer
console.log('choice : ', choice)
// do your algo and put some yield wherever you like
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
}
}
delayer()
However, it is very likely that solving your board is what freezes your ui.
So you want to wait inside of solvingBoard as well:
function* solveBoard () {
let i = 0
while (i < 5) { //freezes the ui if not yielding
yield i++
}
}
function* runAlgo () {
for (let choice = 1; choice <= 9; choice++) {
yield 'choice : '+choice; // gives control back to caller
yield* solveBoard()
}
}
// no need for async, a Promise is already returned
function sleep (t) {
return new Promise((ok, ko) => setTimeout(ok, t))
}
async function delayer () { // need for async so we can use the await keyword below
const it = runAlgo()
let next = it.next()
while (!next.done) {
await sleep(1000)
next = it.next()
console.log('data', next.value)
}
}
delayer()
Related
I am trying to convert a normal function to an async generator function, it needs to have the same props and prototype.
The way that I did it so far was by copying all the descriptors from the async generator function and by using Object.setPrototypeOf
function normalFunc () {
//...
}
async function * asyncGenFunc () {
//...
}
Object.defineProperties(normalFunc, Object.getOwnPropertyDescriptors(asyncGenFunc))
Object.setPrototypeOf(normalFunc, Object.getPrototypeOf(asyncGenFunc))
As I understand it, Object.setPrototypeOf is slow even though I can't see the slowness myself. Is there a better way or is this way not slow in the specific scenario and the tip in MDN isn't about this case.
EDIT: As for the why do I want to do this, here is a very basic version of the function I am making:
const errorHandle = function (func) {
return function(...args) {
try {
let result
result = func.apply(this, args)
if (isObject(result) && result.catch !== undefined) {
result = result.catch(err => console.error(err))
}
return result
} catch(err) {
console.error(err)
}
}
}
const handledAsyncGenFunc = errorHandle(async function * (){})
I want handledAsyncGenFunc to behave exactly the same as a the original async generator function, but to be error-handled. Don't judge the example too much, I only added the bare minimum for the question.
From what I can gather your after a kind of middleware for async generators.
This is actually easier than you think, you can just create wrapper function that that just passes re yields inside a loop.
eg.
const wait = ms => new Promise(r => setTimeout(r, ms));
async function *gen1() {
yield 1;
await wait(1000);
yield 2;
await wait(1000);
throw "error in gen1";
}
function *gen2() {
yield 1;
throw "error in gen2 (normal generator)";
}
async function *handleError(fn) {
try {
for await (const x of fn) yield x;
// yield * fn;
} catch (e) {
console.log('Caught a generator error (async or normal):');
console.error(e);
//you could also re-throw here
//if your just want to log somewhere
//and keep program logic
//throw e;
}
}
async function test1() {
for await (const a of handleError(gen1())) {
console.log(a);
}
}
async function test2() {
for await (const a of handleError(gen2())) {
console.log(a);
}
}
test1().finally(() => test2());
One thing to note, when you wrap the function you always assume it's an async generator that returned, even if you pass a normal one. This is because await will work with none Promise's, but of course it's impossible for this to work the other way round. IOW: you will need to use for await (x of y) { on the wrapper function and not just for (x of y) {
The following code uses an async iterator to deliver "frames".
But this approach leaves an unused variable (_).
If this is a valid approach, why was while await (a hypothetical feature) not added when for await...of was?
while await would enable the omission of this ignored variable.
const raf = () => new Promise(resolve => requestAnimationFrame(resolve))
const frames = {
async *[Symbol.asyncIterator]() {
while (true) yield raf()
}
}
function createGameLoop(store) {
async function start() {
for await (let _ of frames)
render(store)
}
return { start }
}
why was while await (a hypothetical feature) not added when for await...of was?
Because it adds unnecessary complexity to the language implementation, and is rarely ever useful.
Generators ought to produce values, not nothings. Ignoring the value is easy by not using the variable, as you did. The main use case is getting values, and that's what the syntax was designed for.
They actually would be useful even in your example. Your generator doesn't deliver nothing, it doesn't deliver "frames", it does deliver the high resolution timestamps that requestAnimationFrame calls resolve with, and you should be using them in your render function for smooth delta animation:
function createGameLoop(store) {
async function start() {
for await (const timestamp of frames)
render(store, timestamp)
}
return { start }
}
While I was prototyping some logic I need to implement for my project, I noticed some interesting behaviour with async-await and Promise.
// Notice this one returns a Promise
var callMe = function(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i)
resolve(`${i} is called :)`)
}, (i+1)*1000)
})
}
// But this one doesn't
var callYou = function(i) {
setTimeout(() => {
console.log(i)
}, (i+1)*1000)
}
async function run() {
console.log("Start")
for(let i = 0; i < 3; i++) {
let val = await callYou(i)
# also try with callMe()
#let val = await callMe(i)
console.log(val)
}
console.log("End")
}
run()
With let val = await callYou(i), the result looks something like this
Start
callYou()
callYou()
callYou()
End
0
1
2
whereas with let val = await callMe(i), the result looks something like this
Start
0
0 is called :)
1
1 is called :)
2
2 is called :)
End
I was expecting two functions to behave similarly since async function essentially returns a promise. Could someone shed light on why this is happening please?
async functions return promises but neither callYou or callMe are async functions and even if they where, callYou will be fulfilled when the function returns not when the callback passes to the setTimeout function is executed.
I currently have 5 functions; each one uses setInterval to wait for an element to load and than clicks the element when it is available which leads to the next webpage. These functions also take place inside of a while loop. It is very important that these functions take place one after another and the while loop waits for all the functions to complete before looping again. Due to the functions being asynchronous the loop will run x times before any of the functions can even load.
Example of what I am trying to do:
function one () {
var checkForItem = setInterval(function () {
if ($('#element').length) {
$('#element').click();
clearInterval(checkForItem);
}
}, 100);
}
Imagine 5 of these functions (one, two, three, four, five), all with the same format using setInterval and the following while loop:
var x = 0, y = 10;
while (x < y){
one();
two();
three();
four();
five();
x++
}
How would I go about ensuring all the functions take place one after another before having the loop continue?
Note: I have tried using promises although due to the functions being async the loop still continues before the functions complete.
Use async/await syntax with the promises:
function delay(t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async function one() {
while (!$('#element').length) {
await delay(100);
}
$('#element').click();
}
async function main() {
for (var x = 0; x < 10; x++) {
await one();
await two();
await three();
await four();
await five();
}
}
Define the selectors in an array, and gradually iterate over the array in the interval, until the end of the array is reached:
const initialSelectorsToFind = ['#element1', '#element2', '#element3']; // add more as desired
const elementSelectorsToFind = Array.from({ length: 10 })
.reduce(arrSoFar => [...arrSoFar, ...initialSelectorsToFind], []);
let elementIndexToFind = 0;
function tryClick(){
const elementToFind = $(elementSelectorsToFind[elementIndexToFind]);
if (elementToFind.length) {
elementToFind.click();
elementIndexToFind++;
if (elementIndexToFind === elementSelectorsToFind.length) {
clearInterval(tryClickInterval);
}
}
}
const tryClickInterval = setInterval(tryClick, 100);
But if you're trying to trigger a function (such as something that clicks an element) when an element gets added to the DOM, it would be far better off to use something that triggers when the add occurs, such as a callback in the creator function, or MutationObserver
Try wrap promise with async await:
async function one (){
await (new Promise(function(reolve, reject){
var checkForItem = setInterval(function () {
if ($('#element').length) {
$('#element').click();
clearInterval(checkForItem);
resolve();
}
}, 100);
}));
}
//And then in your while loop:
while (x < y){
await one();
await two();
...
x++
}
note: your while loop must be wrapped in an async function also.
As far as I know, async/await is just syntactic sugar over promise.then. Consider this code snippet:
function sleep(n){
return new Promise(res => setTimeout(res, n));
}
function* range(n){
var i = 0;
while(i < n) yield i++;
}
async function doStuff(){
for(let n of range(10)){
console.log(n); // print the number
await sleep(1000); // wait for 1 second
}
}
async/await makes the code very linear, efficient and easy to understand. One thing to keep in mind is that range does not have to have an actual end for this to work.
The problem now is how this can be rewritten using pre-ES7 era's promise.then. Here's a possible implementation of the same loop:
function doStuff(){
return Array.from(range(10)).reduce((acc, ele) => {
return acc
.then(() => console.log(ele))
.then(() => sleep(1000))
}, Promise.resolve());
}
Ignoring the fact that the code isn't quite elegant, the use of Array.from(range(10))
creates an extra array that isn't needed, and
assumes range(10) will end some point in the future.
Doesn't look like a good conversion.
We can also completely reinvent the wheel by using yield as await, but that would make the syntax non ES5-compliant. The goal here is to:
Rewrite using ES5-compliant syntax
Use the promise-returning sleep function
Dynamically chain the sleep promise while allowing the iterator to not have an end
doStuff can be chained:
doStuff().finally(cleanUp); // clean up if something failed
(Optional) Code should not be overly complex
Any idea?
I think the following may do the trick, your example doesn't show what to do with resolve value and how it relates to the iterator values so I made a change to how sleep is called.
Some promise polyfils may run out of stack space with high promise chains so you should check your polyfil (if its implementation returns and continues with a setTimeout the stack should clear but some polyfils may not implement it this way).
function sleep(n){
return new Promise(res => setTimeout(_=>res(n/100), n));
}
function* range(n){
var i = 0;
while(i < n) yield i++;
}
function doStuff(){
const processValue =
resolve => {
console.log("resolved with:",resolve);
// if(resolve===3){throw "nope";}
return sleep(resolve*100);
},
rec = (p,iter) => {
const result = iter.next();
if (result.done){
return p;
}
p = p.then(_=>processValue(result.value))
return p.then(
resolve=>{
return rec(p,iter)
}
);
},
iter = range(10),
firstResult = iter.next();
if(firstResult.done){
return processValue(firstResult.value);
}
return rec(processValue(firstResult.value),iter);
}
doStuff()
.then(
x=>console.log("done:",x)
,reject=>console.warn("fail:",reject)
);
I've always said that if you need an asynchronous design pattern first look at the async library. In this case, since you're using promises, take a look at the promisified async-q library. The translation is straight forward:
var n = 0;
async.whilst(() => n < 10, () => {
n++;
console.log(n);
return sleep(1000);
})