Search code examples
javascriptclosuressettimeoutevent-loop

JS print 0 - 99 or 99 - 0 in order


all. I'm new to JS. Here I met a JS question showed below:

function print(n) {
    setTimeout(() => {
        console.log(n);
    }, Math.floor(Math.random() * 1000));
}
for (var i = 0; i < 100; i++) {
    print(i);
}

The requirement is:

  1. You need to change the program to make it print 0-99 or 99-0 in order;
  2. You cannot use global variables;
  3. You can only change the code inner setTimeout;
  4. You cannot modify Math.floor(Math.random() * 1000.

Here I get some solutions for this question, but there are some of these answers I don't understand, so I hope you guys can help me why this can work. I understand the answers for the first one, the second one and the last one, but I am not quite clear why the rest of the answer can work. Especially for the third one, if I remove the code return ()=>{}, the code still works but it will report an error 100Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src <URL> <URL> 'self' 'unsafe-inline' https:"..

//1.
function print1(n) {
    setTimeout(() => {
        console.log(n);
    }, 1, Math.floor(Math.random() * 1000));
}

// 2. 
function print2(n) {
    setTimeout(() => {
        console.log(i--);
    }, Math.floor(Math.random() * 1000));
}

// 3.
function print3(n) {
    setTimeout((() => {
        console.log(n)
        return () => { }
    }).call(n, []), Math.floor(Math.random() * 1000));
}
for (var i = 0; i < 100; i++) {
    print(i);
}

//4.
function print4(n) {

    setTimeout(() => {

        setTimeout(() => {
            console.log(n);
        }, 1000 * n);

    }, Math.floor(Math.random() * 1000));
}

// 5. 
function print5(n) {
    setTimeout((() => {
        console.log(n);
    })(), Math.floor(Math.random() * 1000));
}

Solution

  • The third approach essentially calls a function as soon as the setTimeout line runs, before the timeout callback runs. So when print(i); is called, the number is logged immediately, before the next iteration of the loop starts.

    Functionally, it's similar to this:

    function print3(n) {
        console.log(n);
        setTimeout(<unimporant>);
    }
    

    It works because the function inside the timeout is invoked immediately with the .call:

    setTimeout((() => {
        console.log(n)
        return () => { }
    }).call(n, []),
    

    simplifies to

    setTimeout((() => {
        console.log(n)
        return () => { }
    })(),
    

    which is basically just

    console.log(n)
    setTimeout((() => {
        return () => { }
    })(),
    

    or, substituting the returned function from the IIFE:

    console.log(n)
    setTimeout(() => {},
    

    The callback passed to setTimeout doesn't do anything.

    if I remove the code return ()=>{}, the code still works but it will report an error

    It sounds like you returned a string instead of the function at the end, which will result in the interpreter trying to run the string. While it's possible to do such a thing in some environments, it's basically the same thing as eval.

    setTimeout('console.log("never do this");');

    which is why the warning comes up.