skip to Main Content
const regExpSync = (str, pat, fn) => {
  let loc = 0;
  let res;
  while((res = pat.exec(str.substring(loc)))){
    const rtn = fn(res);
    if(rtn) return rtn;
    loc += res.length + res.index;
  }
};

const regExpAsync = async (str, pat, fn) => {
  let loc = 0;
  let res;
  while((res = pat.exec(str.substring(loc)))){
    const rtn = await fn(res);
    if(rtn) return rtn;
    loc += res.length + res.index;
  }
};

const testPat = new RegExp('[a-z]+');
const testStr = '------aa-----abc--------zz--------dd----------';

console.log('-');
regExpSync(testStr, testPat, ([str]) => { console.log(str); return false; });
console.log('-');
await regExpAsync(testStr, testPat, async ([str]) => { console.log(str); return false; });
console.log('-');

I have almost identical async and synchronous code.

This is just an example, in reality it is more complex logic that has nothing to do with regular expressions.

Anyway, both work as callback function fn.

I am modifying both whenever there is a change in that logic.

Any ideas on minimizing that modification?

I tried the following but it’s not very convenient.

Can we increase code reuse even just a little bit more here?

const regExp = (str, pat, fn) => {
  if(fn.constructor.name == 'AsyncFunction') return (async () => {

    let loc = 0;
    let res;
    while((res = pat.exec(str.substring(loc)))){
      const rtn = await fn(res);
      if(rtn) return rtn;
      loc += res.length + res.index;
    }

  })();
  else {

    let loc = 0;
    let res;
    while((res = pat.exec(str.substring(loc)))){
      const rtn = fn(res);
      if(rtn) return rtn;
      loc += res.length + res.index;
    }
    
  }
};

2

Answers


  1. You can improve the code reuse by separating the common logic into a helper function and then utilizing that helper function in both the synchronous and asynchronous versions.

    const regExpSync = (str, pat, fn) => {
      return regExpMatch(str, pat, fn);
    };
    
    const regExpAsync = async (str, pat, fn) => {
      return regExpMatch(str, pat, async (res) => {
        return await fn(res);
      });
    };
    
    const regExpMatch = (str, pat, fn) => {
      let loc = 0;
      let res;
      while ((res = pat.exec(str.substring(loc)))) {
        const rtn = await fn(res); //Updated line
        if (rtn) return rtn;
        loc += res.length + res.index;
      }
    };
    
    
    

    Link In the above code, you can extract the common matching logic into the regExpMatch function, which takes the input string, the pattern, and the callback function fn. This function is then used by both regExpSync and regExpAsync.

    Login or Signup to reply.
  2. You can use a generator function to abstract out the looping:

    function* regExp(str, pat) {
      let loc = 0;
      let res;
      while((res = pat.exec(str.substring(loc)))){
        yield res;
        loc += res.length + res.index;
      }
    }
    function regExpSync(str, pat, fn) {
      for (const res of regExp(str, pat)) {
        const rtn = fn(res);
        if(rtn) return rtn;
      }
    }
    async function regExpAsync(str, pat, fn) {
      for (const res of regExp(str, pat)) {
        const rtn = await fn(res);
        if (rtn) return rtn;
      }
    }
    

    Notice that with the iterator returned by regExp, you can even simplify this more by using iterator helper methods:

     const regExpSync = (str, pat, fn) => regExp(str, pat).some(fn);
     // or strictly
     const regExpSync = (str, pat, fn) => regExp(str, pat).map(fn).find(Boolean);
    
     const regExpAsync = (str, pat, fn) => AsyncIterator.from(regExp(str, pat)).some(fn);
    

    However, if that doesn’t fit your bill, you can actually use a generator function for the whole logic with yield where a promise might need to be awaited, then run it either synchronously (just skipping the yielded value and passing it back in immediately) or asynchronously (by waiting for the yielded promise and passing/throwing its result back in):

    function regExp*(str, pat, fn) {
      let loc = 0;
      let res;
      while((res = pat.exec(str.substring(loc)))){
        const rtn = yield fn(res);
        if(rtn) return rtn;
        loc += res.length + res.index;
      }
    }
    function syncRun(generatorFn) {
      return function(...args) {
        const generator = generatorFn.apply(this, args);
        let result = {done: false, value: undefined};
        while (!result.done) {
          result = generator.next(result.value);
        }
        return result.value;
      };
    }
    function asyncRun(generatorFn) {
      return async function(...args) {
        const generator = generatorFn.apply(this, args);
        let method = "next", done = false, value = undefined;
        while (!done) {
          ({value, done} = generator[method](value));
          [method, value] = await Promise.resolve(value).then(
            result => ["next", result],
            error => ["throw", error]
          );
        }
        return value;
      };
    }
    
    const regExpSync = syncRun(regExp);
    const regExpAsync = asyncRun(regExp);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search