-1

I'm figuring out how to prevent any blocking code from blocking the main event loop.

Suppose there's a function (let's assume it's going to block the event loop if implemented synchronously):

function reverseString(name){
    let revString='';
    for(let i=name.length;i>=0;i--){
        revString+=name[i];
    }
    return revString;
}

If I use a callback and modify it to:

function reverseString(name, callback){
    let revString='';
    for(let i=name.length;i>=0;i--){
        revString+=name[i];
    }
     callback(revString);
}

It's still a synchronous callback.

How do I convert the function into its asynchronous version using a callback?

12
  • 1
    What do you mean by "convert the function into its asynchronous version"? It depends on when you want the callback to run, if not straight away (synchronously). There are built-in functions for the common cases, like setTimeout, or fetch. You can't write your own "asynchronous primitive" like these though. Commented Sep 18, 2022 at 10:57
  • 1
    I think you've not clear in your mind what synchronous and asynchronous mean. Asynchronous means that other operations can be executed while waiting for a task to complete, this task is usually a timeout or an I/O operation. In your case, it's just a loop, so there's no reason to make your function async since the CPU is busy the 100% of its execution. Don't forget that JS is single-threaded Commented Sep 18, 2022 at 10:58
  • 2
    This seems to be an XY Problem. Functions are made asynchronous because they do something that takes a long time and it would be a problem if they blocked the rest of the JS program from running while they did whatever they did. The code you have isn't long running, its really quick (at least in most cases, it might take more than a desired amount of time if you used the complete text of War and Peace as the input). Adding asynchronicity would be adding pointless complexity. What problem are you really trying to solve? Commented Sep 18, 2022 at 11:02
  • Hi, I used string reversal just for an example's sake. Suppose instead of having to reverse a string we needed to calculate the Fibonacci sequence till a given number. That would be blocking code and need to be asynchronous. How would we change a synchronous function performing that task to an asynchronous one? Commented Sep 18, 2022 at 11:04
  • 2
    @smac89 - No, it's an awful solution. The blocking code still blocks the main event loop, it just waits until the current set of operations is over before doing so. Then the blocking code will run and, for example, a click event handler won't respond until the calculation is over. Commented Sep 18, 2022 at 11:08

4 Answers 4

2

There is no simple, straight-forward way to make any code yield to the browser (or Node.js) event loop at any given point, but something you can do is

  • make the function async so it returns a promise and becomes interruptible
  • strew it with awaits to something that allows the event loop to do something else every now and then (and have the event loop itself resume your function); here that "something" is just a promisified setTimeout.
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function reverseString(str) {
  let revString = '';
  for (let i = str.length - 1; i >= 0; i--) {
    revString += str[i];
    console.log(`Reversing character ${i} of ${str}`);
    await delay(0);
  }
  return revString;
}

reverseString('Hernekeitto').then(console.log);
reverseString('Viina').then(console.log);
reverseString('Teline').then(console.log);
reverseString('Johannes').then(console.log);

This will print out (e.g.)

Reversing character 10 of Hernekeitto
Reversing character 4 of Viina
Reversing character 5 of Teline
Reversing character 7 of Johannes
Reversing character 9 of Hernekeitto
Reversing character 3 of Viina
Reversing character 4 of Teline
Reversing character 6 of Johannes
Reversing character 8 of Hernekeitto

so you can see that all 4 calls are being done asynchronously.

Of course, this will also be potentially a lot slower than the synchronous, blocking code; you could make it yield every n items or every n milliseconds, for example...

Sign up to request clarification or add additional context in comments.

Comments

2

You can move processing off the main event loop by using a Worker.

HTML document

<!DOCTYPE html> 
<title>Worker Example</title>

<input />
<button>Reverse</button>
<output />

<script>
    const reverser = new Worker('reverser.js');
    reverser.onmessage = e => {
        document.querySelector('output').value = e.data;
    };
    const reverseString = () => {
        const string = document.querySelector('input').value;
        reverser.postMessage({ string });
    };
    document.querySelector('button').addEventListener('click', reverseString);
</script>

reverser.js

onmessage = e => {
    const result = reverseString(e.data.string);
    postMessage(result);
};

function reverseString(name) {
    let revString = '';
    for (let i = name.length - 1; i >= 0; i--) {
        revString += name[i];
    }
    return revString;
}

(Note: Your loop for reversing was broken; I fixed it for this example).

2 Comments

This will block the worker, though. ;-)
@AKX — Of course, but the question was about unblocking the main event loop
-2

You can try a combination of setTimeout and Promise:

async function reverseString(name, callback){
  return new Promise(resolve => {
    setTimeout(() => {
      let revString='';
      for(let i=name.length;i>=0;i++){
        revString+=name[i];
      }
      resolve(revString);
    }, 0);
  }).then(callback);
}

The above function is promise-based, but whether or not it is asynchronous is something else. I believe only IO can truly run concurrently either on Web or in NodeJS. The above simply schedules the function not to run immediately, but when it does run it will still behave like the synchronous version.


An example

Below is an example of using callbacks, but also notice that the although the code is still calculating fibonacci(40), you can still click the button to increment a number. Admittedly, you will notice that the button becomes unresponsive at times, if you click it fast enough, but the point is to show that it is possible to achieve what you are after without (totally) blocking the main event loop.

const {
  useState,
  useCallback,
  useEffect
} = React;

const App = () => {
  const [counter, setCounter] = useState(0);
  const [fib40, setFib40] = useState(null);

  const fibonacci = useCallback((fib, cb) => {
    if (fib === 1 || fib === 0) {
      cb(1);
    }

    setTimeout(() => {
      fibonacci(fib - 1, (fib1) => {
        setTimeout(() => {
          fibonacci(fib - 2, (fib2) => {
            cb(fib1 + fib2);
          });
        }, 0);
      });
    }, 0);
  }, []);

  useEffect(() => {
    fibonacci(40, (result) => {
      console.log("Fibonacci 40:", result);
      setFib40(result);
    });
  }, [fibonacci]);

  return (
    <div>
      <p>{counter}</p>
      <button onClick={() => {setCounter(count => count + 1)}}>Click me</button>
      <p>Fib 40 {fib40 == null ? "pending..." : fib40}</p>
    </div>
  );
}

ReactDOM.render( <App/>, document.getElementById("root"));
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

<body>
  <div id="root"></div>
</body>

Comments

-2

use setTimeout to fake asynchronous operation.

setTimeout(function() {
  callback(revString);
}, 500)

For fun, let's try something tricky and assume the reverse function itself is asynchronous. Maybe indeed that was the purpose of the original question.

function reverseString(name, callback) {
  if (name.length <= 1) {
    callback(name);
    return;
  }

  var arr = name.split('');
  var last = arr.pop();
  var start = arr.join('');

  reverseString(start, function(result) {
    console.log(result)
    
    setTimeout(function() {
      callback(last + result)
    }, 100)

  })
}


reverseString('dlrow olleh', console.log);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.