Home > Software engineering >  Prevent code from blocking the event loop
Prevent code from blocking the event loop

Time:09-19

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?

CodePudding user response:

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...

CodePudding user response:

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).

CodePudding user response:

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>

CodePudding user response:

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);

  • Related