Как динамически загружать несколько скриптов в определенном порядке?

#javascript #typescript

#javascript #typescript

Вопрос:

Мне нужно динамически загружать x скриптов (скажем, 6 для этого примера) (либо в TypeScript, либо в JavaScript).

Первые 2 скрипта используются последними 4. Поэтому я должен убедиться, что скрипты 1 и 2 загружены до загрузки последних 4.

 var scripts = [
  { "path": "f1.js", "isDependency": true },
  { "path": "f2.js", "isDependency": true },
  { "path": "f3.js", "isDependency": false },
  { "path": "f4.js", "isDependency": false },
  { "path": "f5.js", "isDependency": false },
  { "path": "f6.js", "isDependency": false }
];
  

И у меня есть асинхронная функция, которая загружает скрипты, но у меня возникают проблемы с настройкой async / await или promises, которые ожидают загрузки первых 2 скриптов, а затем позволяют остальным загружаться в любом порядке. порядок.

 private async loadJsFileAsync(filePath: string) {
  return new Promise((resolve, reject) => {
    if (!document.getElementById(filePath)) {
      var script = document.createElement('script');

      script.onload = function () {
        // mark with attribute so we know that it's loaded
        script.attributes["data-loaded"] = "true";
        resolve();
      }
      script.onerror = function (err) {
        reject(err);
      }

      script.type = "text/javascript";
      script.src = filePath
      script.id = filePath;
      document.getElementsByTagName('head')[0].appendChild(script);

    } else {
      // file processed, but check to see it actually loaded
      var s1 = <HTMLScriptElement>document.getElementById(filePath);
      var loadedAttribute = s1.attributes["data-loaded"] === "true";
      if (loadedAttribute) {
        resolve();
      } else {

        // add event listeners (in addition to the existing ones)
        s1.addEventListener("load", function () {
          resolve();
        });
        s1.addEventListener("error", function (err) {
          reject(err);
        });
      }
    }
  });
}
  

На данный момент я использую асинхронную функцию только с одним требуемым скриптом следующим образом:

 this.loadJsFileAsync("f1.js").then(value => {

  // load other files
  loadJsFile("f3.js");
  loadJsFile("f4.js");
  loadJsFile("f5.js");
  loadJsFile("f6.js");
});

private loadJsFile(filePath: string) {
  var script = document.createElement('script');
  script.type = "text/javascript";
  script.src = filePath   this.version();
  script.id = filePath;
  document.getElementsByTagName('head')[0].appendChild(script);
}   
  

Комментарии:

1. Можете ли вы опубликовать свое использование функции? (Как вы используете await)

2. @mwilson Прямо сейчас я просто использую функцию с одним требуемым скриптом. Опубликовано.

Ответ №1:

Прежде всего, почему бы не использовать requirejs или какой-либо другой загрузчик модулей? Они предоставляют вам возможность объединять ваши скрипты в цепочки, чтобы сначала загружались зависимости. Во-вторых, я думаю, это то, что вы хотите:

Здесь определенно есть место для улучшения и оптимизации, но это должно дать вам результат, который вы ищете.

 class Script {
    constructor(path, dependencyOrder) {
        this.path = path;
        this.dependencyOrder = dependencyOrder;
    }
}
class ScriptLoader {
    constructor() {
        this.scripts = [];

    }
    addScript(script) {
        const exists = this.scripts.find( s => s.path.toLowerCase() === script.path.toLowerCase());
        if (!exists) {
            this.scripts.push(script);
        }
    }
    async loadScripts() {
        if (Array.isArray(this.scripts)) {
            const orderedScripts = this.orderScriptsByDependency();
            let scriptsLoaded = false;
            let counter = 0;
            while (!scriptsLoaded) {
                const _script = orderedScripts[counter]
                await this.loadScript(_script, _script.dependencyOrder > -1);
                counter  = 1;
                scriptsLoaded = counter === orderedScripts.length;
            }
        }
    }
    async loadScript(script, waitToLoad) {
        return new Promise( (resolve, reject) => {
            const scriptTag = document.createElement('script');
            scriptTag.src = script.path;
            if (waitToLoad) {
                scriptTag.async = true;
                document.body.appendChild(scriptTag);
                scriptTag.onload = () => { resolve(); }
            } else {
                document.body.appendChild(scriptTag);
                resolve();
            }
        } );
    }
    orderScriptsByDependency() {
        if (Array.isArray(this.scripts)) {
            return this.scripts.sort( (a, b) => {
                return a.dependencyOrder - b.dependencyOrder;
            });
        }
        return null;
    }
}
  

Использование

 class IndexView {
    constructor() {
        this.loader = new ScriptLoader();
        this.init();
    }
    async init() {
        this.loader.addScript(new Script('scripts/script0.js', 0));
        this.loader.addScript(new Script('scripts/script1.js', 1));
        this.loader.addScript(new Script('scripts/script2.js', 2));
        await this.loader.loadScripts();
    }
}
  

Requirejs

Комментарии:

1. упс. Забыл вернуть значение в моей функции сортировки. Обновление сейчас

Ответ №2:

Если вы хотите сохранить порядок, вы можете использовать onload of HtmlScriptElement подобным образом.

 const loadScripts = (urls: string[]): void => {
  urls.forEach((url, index) => {
    const js = loadScript(url);
    if (index < urls.length) {
      js.onload = () => {
        loadScript(urls[index   1]);
      };
    }
  });
};

const loadScript = (url: string): HTMLScriptElement => {
  const js = document.createElement("script");
  js.setAttribute("src", url);
  document.body.appendChild(js);
  return js;
};
  

И назовите это так:

 loadScripts(["script1.js", "script2.js", "script3.js"]);
  

Комментарии:

1. Это позволит загружать каждый отдельный скрипт по одному. Это долгое время загрузки, если у вас много скриптов…

2. Да — ваше решение лучше с асинхронной загрузкой.

3. @mwilson Я добавил ответ с помощью функции jQuery, которая загружает скрипты, не связывая их.