我最近不得不纠正 Web 应用程序(不是我创建的)中的安全问题。安全问题是,它使用的是非 http-only cookie。所以我不得不设置会话 cookie 仅限 http,这意味着你不能再从 javascript 读取(和设置)cookie 的值。到目前为止非常容易。
更深层次的问题是,使用的网络应用程序
JSON.parse(readCookie(cookieName)).some_value
在一百万个地方。
因此,为了不必重写“一百万行代码”,我不得不创建一个 ajax 端点,将 http-cookie 的内容作为 JSON 提供给我,并重写 readCookie 以使用 同步 ajax 请求(而不是读取 cookie ), 因为其余可怕的代码期望 readCookie 在这百万个地方是同步的,因为读取 cookie 是同步的。
现在的问题是,我得到了很多
主线程上的同步 XMLHttpRequest 已弃用,因为它会对最终用户的体验产生不利影响。如需更多帮助,请查看 https://xhr.spec.whatwg.org/ 。
这会向调试控制台发送垃圾邮件,更不用说有人决定删除此功能的可能性了。
因此,我正在研究新的 ES async/await 关键字,看看它是否能以某种方式帮助同步发出异步 ajax 请求(我知道我必须为 IE 11 使用包装器)。
到目前为止,我阅读了这些页面
https://www.twilio.com/blog/2015/10/asyncawait-the-hero-javascript-deserved.html
https://pouchdb.com/2015/03/05/taming-the-async-beast-with-es7.html
https://jakearchibald.com/2014/es7-async-functions/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function *
但看起来所有新的异步东西似乎只解决了更容易编写异步代码的问题,而不是在异步代码和现有同步代码之间实现互操作。使用我阅读的信息,我现在可以等待异步 ajax 调用的结果,就像它是同步的一样,但问题是 - 只允许在异步方法中等待……这意味着即使我可以像它一样等待结果是同步的,getCookie 方法仍然必须是异步的,这使得所有的东西看起来完全没有意义(除非你的整个代码都是异步的,当你不从头开始时肯定不是)。 .
我似乎无法找到有关如何在同步代码和异步代码之间进行互操作的任何信息。
例如,在 C# 中,我可以使用 .Result 从同步上下文调用异步方法,例如
AsyncContext.RunTask(MyAsyncMethod).Result;
或者更容易但不太安全的死锁
MyAsyncMethod(args).Result;
有什么方法可以在 JavaScript 中实现相同的目的吗?
当代码库的其余部分是同步的,没有任何互操作的可能性时,传播异步似乎没有什么意义……在公元 2017 年的 JavaScript 中真的仍然没有办法实现这一点吗?
我再次强调:
我知道 如何进行同步 ajax 调用, 我知道 如何使用带有回调和/或承诺的异步 ajax 调用。
但是我无法弄清楚的是如何同步异步 ajax 调用 (无回调) ,以便可以从期望 同步 运行的代码中使用它(在“一百万个地方”)!
到目前为止,这是我尝试过的:
(请注意,无论我使用 loadQuote
还是 main
_,文本_“Ron once said” 仍然首先出现在调试控制台中,如果 异步 ajax 调用则不会出现这种情况已 同步 解决)
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="google" value="notranslate" />
<!--
<meta name="author" content="name" />
<meta name="description" content="description here" />
<meta name="keywords" content="keywords,here" />
<link rel="shortcut icon" href="favicon.ico" type="image/vnd.microsoft.icon" />
<link rel="stylesheet" href="stylesheet.css" type="text/css" />
-->
<title>Title</title>
<style type="text/css" media="all">
body
{
background-color: #0c70b4;
color: #546775;
font: normal 400 18px "PT Sans", sans-serif;
-webkit-font-smoothing: antialiased;
}
</style>
<script type="text/javascript">
<!--
// http://localhost:57566/foobar/ajax/json.ashx
var ajax = {};
ajax.x = function () {
if (typeof XMLHttpRequest !== 'undefined') {
return new XMLHttpRequest();
}
var versions = [
"MSXML2.XmlHttp.6.0",
"MSXML2.XmlHttp.5.0",
"MSXML2.XmlHttp.4.0",
"MSXML2.XmlHttp.3.0",
"MSXML2.XmlHttp.2.0",
"Microsoft.XmlHttp"
];
var xhr;
for (var i = 0; i < versions.length; i++) {
try {
xhr = new ActiveXObject(versions[i]);
break;
} catch (e) {
}
}
return xhr;
};
ajax.send = function (url, callback, method, data, async) {
if (async === undefined) {
async = true;
}
var x = ajax.x();
x.open(method, url, async);
x.onreadystatechange = function () {
if (x.readyState == 4) {
callback(x.responseText)
}
};
if (method == 'POST') {
x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
}
x.send(data)
};
ajax.get = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, 'GET', null, async)
};
ajax.post = function (url, data, callback, async) {
var query = [];
for (var key in data) {
query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
}
ajax.send(url, callback, 'POST', query.join('&'), async)
};
///////////
function testAjaxCall() {
ajax.get("./ajax/json.ashx", null, function (bError, strMessage, iStatus)
{
console.log("args:", arguments);
console.log("Error:", bError);
console.log("Message:", strMessage);
console.log("Status:", iStatus);
}
, true
);
}
-->
</script>
</head>
<body>
<script type="text/javascript">
function getQuote() {
var quote;
return new Promise(function (resolve, reject) {
ajax.get("./ajax/json.ashx", null, function (bError, strMessage, iStatus) {
// console.log("args:", arguments);
// console.log("Error:", bError);
// console.log("Message:", strMessage);
// console.log("Status:", iStatus);
quote = bError;
resolve(quote)
}, true);
/*
request('./ajax/json.ashx', function (error, response, body) {
quote = body;
resolve(quote);
});
*/
});
}
async function main() {
var quote = await getQuote();
console.log("quote: ", quote);
}
function myGetQuote() {
var quote = async function () { return await getQuote(); };
console.log("quote: ", quote);
return quote;
}
function spawn(generatorFunc) {
function continuer(verb, arg) {
var result;
try {
result = generator[verb](arg);
} catch (err) {
return Promise.reject(err);
}
if (result.done) {
return result.value;
} else {
return Promise.resolve(result.value).then(onFulfilled, onRejected);
}
}
var generator = generatorFunc();
var onFulfilled = continuer.bind(continuer, "next");
var onRejected = continuer.bind(continuer, "throw");
return onFulfilled();
}
function loadQuote()
{
return spawn(function *() {
try {
let story = yield getQuote();
console.log("story:", story);
// addHtmlToPage(story.heading);
// for (let chapter of story.chapterURLs.map(getJSON)) { addHtmlToPage((yield chapter).html); } addTextToPage("All done");
} catch (err) {
//addTextToPage("Argh, broken: " + err.message);
console.log("Argh, broken: " + err.message);
}
//document.querySelector('.spinner').style.display = 'none';
});
}
function autorun()
{
console.clear();
// main();
// main();
loadQuote();
//var quote = myGetQuote();
// console.log("quote: ", quote);
console.log('Ron once said,');
}
if (document.addEventListener) document.addEventListener("DOMContentLoaded", autorun, false);
else if (document.attachEvent) document.attachEvent("onreadystatechange", autorun);
else window.onload = autorun;
</script>
</body>
</html>
原文由 Stefan Steiger 发布,翻译遵循 CC BY-SA 4.0 许可协议
确切地说,不,没有解决方法。 JavaScript 的运行到完成语义 要求 同步函数在任何挂起的异步操作(例如异步 XHR 调用的回调到 XHR 处理程序)可以运行之前完成。
JavaScript 在给定线程上运行的方式是它处理作业队列1 :
(它比那复杂一点,它有两个层次,但这与这个特定问题无关。)
XHR 完成等是在队列中安排的作业。没有办法暂停一个作业,从队列中运行另一个作业,然后选择暂停的作业。
async
/await
为处理异步操作提供了非常简单的 _语法_,但它们不会改变作业队列的性质。对于您的情况,我看到的唯一解决方案是一直异步到顶层。这可能并不像您想象的那么复杂(或者可能会如此)。在许多情况下,它会在很多功能的
async
前面添加function
。但是,使这些函数异步可能会产生重大的连锁反应(例如,事件处理程序中同步的内容变为异步会改变与 UI 相关的事件发生的时间)。例如,考虑这个同步代码:
现在我们要制作 make
doThat
async( 注意:只适用于支持async
/ —706ad99c7a530d5765e01a730094-d246-的最新浏览器 — /await
d246-stack ‘包括它们,所以我们不能使用该选项):关键是我们在
doSomething
中尽快进入异步(因为handler
不能异步)。但是,当然,这会改变与处理程序相关的工作时间。 (当然,我们可能应该更新handler
以捕获承诺 `doSomething() 返回的错误。)1这是 JavaScript 规范术语。 HTML5 规范(也涉及到这一点)将它们称为“任务”而不是“工作”。