2
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;
    }
    
  }
};

enoeht
  • 241
  • 2
  • 9
  • 1
    you can `await` a non-promise (try it) ... that way your function will always return a Promise, rather than what your suggestion does, i.e. can return a Promise or a value - the short version, `regExpAsync` will work regardless if `fn` is sync or async - really, try it – Jaromanda X Jul 28 '23 at 05:18

2 Answers2

3

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);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Thank you very much. It works perfectly. I still have so much to learn. – enoeht Jul 28 '23 at 06:06
  • 1
    @enoeht The second part is *very* advanced usage of generators. It was used in the olden days (before `async`/`await`, before native promises) in promise libraries [to emulate `await`](https://stackoverflow.com/a/30412920/1048572), allow the library user to pass such a promise-yielding "coroutine" to a helper function and have it run asynchronously – Bergi Jul 28 '23 at 06:51
  • I agree. That generator is too difficult for me. But the concept of using the ```yield``` keyword you taught me gave me a new idea. I only studied it briefly, but I think that eliminates the need to prepare two versions in the first place. Perhaps in my case I don't even need a generator. That will be studied later when I need it. Thank you very much. There will be massive revisions to my past work. (Is this really a good thing? LOL) – enoeht Jul 28 '23 at 07:14
-2

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.

  • 1
    …except this doesn't work, as in the async version `fn` still returns a promise – Bergi Jul 28 '23 at 05:20
  • Your update still doesn't work - you cannot use `await` without marking the function as `async`, and if you did that, you could no longer use it synchronously. Which is the whole dilemma the OP faces. – Bergi Jul 28 '23 at 05:35