DEV Community

String.prototype.replace asynchronously?

YCM Jason on April 28, 2018

Original post: https://www.ycmjason.com/blog/2018/04/28.html this article assumes basic knowledge of RegExp. Background I was workin...
Collapse
 
anonyco profile image
Jack Giffin • Edited

Your asynchronous String.prototype.replace function is very nice, but I can see three major ways to massively improve its performance.

  1. It calls await unnecessarily. What it should do is be maybe-asynchronous where it first checks to see if any promises were returned. (See stackoverflow.com/questions/522217...)

  2. The V8 engine has recently upgraded its string concatenation operator, making the need to prepackage strings into lists slower than simple string appendments.

  3. It should use callbacks where possible because as pretty as await/promise/async looks, the W3C really botched their performance. Permanently. Because, await/promise/async are all required to wait until the next tick before executing. What the W3C should have done is add in a delay keyword that can be added onto await and async for delay await and delay async, then an extra delay argument on the Promise and this delay would tell the browser whether or not it really needs to wait until the next tick.

Putting the top three together, let us witness my version. Notice how I do not depend on the promise's ability to be deferred to the next tick. This is intentional. If we could all program like this, then the W3C could change its errant standard toward not requiring browsers to delay the process to the next tick.

For more details on why I am making such a huge fuss over having to be delayed to the next tick, please see stackoverflow.com/questions/854777...

const asyncStringReplace = (str, regex, aReplacer, selfArg) => {
    const substrs = [], accstr = "";
    let resetIndex = 0;
    let match;
    let pendingPromises = 0;
    let accept = null;
    for(let match=regex.exec(str); match!==null; match=regex.exec(str)){
        if (resetIndex === regex.lastIndex) {
            regex.lastIndex += 1;
            continue;
        }
        // put non matching string
        substr += str.slice(resetIndex, match.index);
        // call the async replacer function with the matched array
        const retValue = aReplacer.apply(selfArg, match);
        if (retValue instanceof Promise) {
            const index = substrs.push(accstr, "") - 1;
            accstr = "";
            pendingPromises += 1;
            const thenAfter = returnValue => {
                substrs[index] = returnValue += "";
                pendingPromises -= 1;
                if (pendingPromises === 0 && accept !== null)
                    accept(substrs.join(""));
            };
            retValue.then(thenAfter, thenAfter);
        } else {
            accstr += retValue;
        }
        resetIndex = regex.lastIndex;
    }
    accstr += str.substring(resetIndex);
    // wait for aReplacer calls to finish and join them back into string
    if (pendingPromises === 0) {
        return accstr;
    } else {
        // put the rest of str
        substrs.push( accstr );
        accstr = "";
        return new Promise(function(acceptFunc){
            accept = acceptFunc;
            if (pendingPromises === 0) accept(substrs.join(""));
        });
    }
};
Collapse
 
ycmjason profile image
YCM Jason • Edited

Thanks a lot for sharing your inspiring ideas. đŸģI will keep those in mind in case I encounter any performance issue later. But for now, I would keep my version for readability and elegance. 😉

Collapse
 
cp1797 profile image
Craig Pinto • Edited

Hi!

Could you give a quick example of the aReplacer function? I can't seem to figure it out

Thanks

Collapse
 
ycmjason profile image
YCM Jason

It's a function that return a promise of string. It can be any asynchronous operations. What kind of example would you like? I can make something up if you wish.

Collapse
 
ladifire profile image
Ladifire

// put non matching string
substrs.push(str.slice(i, match.index));

what's "i" ??????????

Collapse
 
ycmjason profile image
YCM Jason

I have edited accordingly. Not sure why this obvious error was never spotted. Thank you!