wrapAsync = (thisArg, func, preprocessorFunc) => (
    (...args) => {
      let resolve;
      let reject;
      /* Using resolve/reject to call API in the scope of this function, not inside Promise,
         because an API validation exception is thrown synchronously both in Chrome and FF
         so the caller can use try/catch to detect it like we've been doing in icon.js */
      const promise = new SafePromise((_resolve, _reject) => {
        resolve = _resolve;
        reject = _reject;
      });
      // Make the error messages actually useful by capturing a real stack
      const stackInfo = new SafeError(`callstack before invoking ${func.name || 'chrome API'}:`);
      // A single parameter `result` is fine because we don't use API that return more
      const cb = result => {
        const runtimeErr = chrome.runtime.lastError;
        const err = runtimeErr || (
          preprocessorFunc
            ? preprocessorFunc(resolve, result)
            : resolve(result)
        );
        // Prefer `reject` over `throw` which stops debugger in 'pause on exceptions' mode
        if (err) {
          if (!runtimeErr) stackInfo[STACK] = `${err[1]}\n${stackInfo[STACK]}`;
          stackInfo[MESSAGE] = runtimeErr ? err[MESSAGE] : `${err[0]}`;
          stackInfo.isRuntime = !!runtimeErr;
          reject(stackInfo);
        }
      };
      if (process.env.IS_INJECTED) {
        safePush(args, cb); /* global safePush */
        try {
          safeApply(func, thisArg, args);
        } catch (e) {
          if (e[MESSAGE] === 'Extension context invalidated.') {
            /* global logging */// only used with process.env.IS_INJECTED=content
            logging.error(`Please reload the tab to restore ${VIOLENTMONKEY} API for userscripts.`);
          } else {
            throw e;
          }
        }
      } else {
        /* Not process.env.IS_INJECTED */// eslint-disable-next-line no-restricted-syntax
        thisArg::func(...args, cb);
      }
      if (process.env.DEBUG) promise.catch(err => console.warn(args, err?.[MESSAGE] || err));
      return promise;
    }
  )
@param {Object} thisArg - original API group
@param {function} func - original API function
@param {WrapAsyncPreprocessorFunc} [preprocessorFunc] - modifies the API callback's response