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

Posted

技术标签:

【中文标题】我可以使用 `fetch` 从另一个运行 JS 脚本吗?【英文标题】:Can I run a JS script from another using `fetch`? 【发布时间】:2017-12-01 21:42:02 【问题描述】:

这里是中级JS/JQ的人。

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

$.getScript( 'xxx.js' );

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

...?

稍后回复梦想家约瑟夫:

试过这个:

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

...它没有运行脚本“generic.js”。你的意思是别的吗?

【问题讨论】:

为什么不动态创建<script> 并弹出该网址? 回调地狱逃逸 => 承诺,异步/等待。 如果你尝试的是“稍后”的实验,那么你误解了他们的意思。 @evolutionbox 确实我做到了......这就是为什么我说“你是不是还有别的意思”。正如我所说,我不是 JS 专家。愿意告诉我他们的意思吗? 【参考方案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 可以组合

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

【讨论】:

谢谢...是的,我刚刚开始接触 Promises 的表面。 ES7“等待”和“异步”的东西看起来很有希望:但在让脚本等待 getScript 回调结束时,我仍然在挣扎……实际上我昨天瞥见了 RxJS。还将看一下requirejs。叹。正如我所说,我不是 JS 专家... "[...] untrackeable side effects and lack of security [...]" 据我所知,eval(await fetch(url).then(r => r.text()))<script src="$url"></script> 的安全性没有区别?【参考方案2】:

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

脚本可以动态附加并用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(() =>  ... );

【讨论】:

哇......这太棒了:你已经澄清了fetch 并且给了我一个解决问题的答案。【参考方案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】:

按照@cnexans 的回答(使用.text() 然后.eval()),遵循fetch() Api 对我来说效果很好。我注意到与添加 &lt;script&gt; 标记的方法相比,性能有所提高

运行代码 sn-p 以查看 fetch() API 加载 async(因为它是 Promise):

// 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>

【讨论】:

【参考方案5】:

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

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

这就是我在 Github 上发布这个小小的 Fetch data loader 的原因。

它将获取的内容加载到目标容器中并运行其脚本(不使用邪恶的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>

【讨论】:

以上是关于我可以使用 `fetch` 从另一个运行 JS 脚本吗?的主要内容,如果未能解决你的问题,请参考以下文章

.catch() 甚至通过 fetch() 从另一个承诺中捕获所有错误

使用JavaScript从另一个页面获取HTML

fetch 未在 D3 csv 调用中定义

从另一台没有工匠的电脑上运行 laravel 服务 css 和 js 不工作 [关闭]

使用 JS Fetch API 上传时有效负载过大错误 (413)。通过邮递员工作正常

从另一个MyISAM表中自动加载MEMORY表