async function fetchResources(script, src) {
  const { custom, meta } = script;
  const { pathMap } = custom;
  const { resources } = meta;
  const icon = custom.icon || meta.icon;
  const jobs = [];
  for (const url of meta.require) {
    jobs.push([S_REQUIRE, url]);
  }
  for (const key in resources) {
    jobs.push([S_CACHE, resources[key]]);
  }
  if (isRemote(icon)) {
    jobs.push([S_CACHE, icon, ICON_TIMEOUT]);
  }
  for (let i = 0, type, url, timeout, res; i < jobs.length; i++) {
    [type, url, timeout] = jobs[i];
    if (!(res = pendingDeps[type][url])) {
      if (url && !isDataUri(url)) {
        url = pathMap[url] || url;
        if ((res = src[type]) && (res = res[url]) != null) {
          storage[type].setOne(url, res);
          res = '';
        } else {
          res = fetchResource(src, type, url);
          if (timeout) res = Promise.race([res, makePause(timeout)]);
          pendingDeps[type][url] = res;
        }
      }
    }
    jobs[i] = res;
  }
  const errors = await Promise.all(jobs);
  const error = errors.map(formatHttpError)::trueJoin('\n');
  if (error) {
    const message = i18n('msgErrorFetchingResource');
    sendCmd('UpdateScript', {
      update: { error, message },
      where: { id: getPropsId(script) },
    });
    return `${message}\n${error}`;
  }
}
@param {VMScript} script
@param {VMScriptSourceOptions} src
@return {Promise<?string>} error text