How can I make jQuery replaceWith() "non-blocking" (async)?

Viewed 250

I'm using the function replaceStr() to replace some strings in the body tag. If the html page is small, the replacing is not noticeable. But if the html page is bigger and more complex you notice it. The replacing is blocking the browser. My question, how is it possible to make the replacing non-blocking? The replacing is not critical to the page, so it can happen in the background, when the browser is not busy. I tried to use async and await, but I think the replaceWith() function can't handle Promises and that's why it's not working with async/await. But how can you do it then?

function replaceStr(myStrArr) {
  const container = $('body :not(script)');
  myStrArr.map((mystr) => {
    container
      .contents()
      .filter((_, i) => {
        return i.nodeType === 3 && i.nodeValue.match(mystr.reg);
      })
      .replaceWith(function () {
        return this.nodeValue.replace(mystr.reg, mystr.newStr);
      });
  });
}

Thank you for your help.

1 Answers

Your current implementation has a few spots where it could be optimized before going the async route. For example you could get rid of the jQuery dependency. It doesn't help much in your case but adds overhead.

Then currently you're mapping over your replacements and for each over all candidate nodes, replacing the nodeValue each time. This possibly triggers a repaint every time.

Instead you could use a TreeWalker to quickly iterate over the relevant nodes, and only update the nodeValues once.


In my tests, the following runs roughly 16 times faster, than your current code. Maybe that's already enough?

function replaceStr_upd(replacements) {
    // create a tree walker for all text nodes
    const it = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        // but skip SCRIPTs
        acceptNode: node => node.parentNode.nodeName === 'SCRIPT'
                            ? NodeFilter.FILTER_REJECT
                            : NodeFilter.FILTER_ACCEPT
    });

    // a helper function
    const applyReplacements = initial => replacements.reduce((text, r) => text.replace(r.reg, r.newStr), initial);

    // iterate over all text node candidates
    while (it.nextNode()) {
        // but only update once per node:
        it.currentNode.nodeValue = applyReplacements(it.currentNode.nodeValue);
    }
}
Related