I recently had to correct security issues in a web-application (that I didn't create). The security problem was, it was using non-http-only cookies. So I had to set the session-cookie http-only, which means you can't read (and set) the cookie's value anymore from javascript. So far so seamingly easy.
The deeper problem was, the web-application used
JSON.parse(readCookie(cookieName)).some_value
on a million places.
So in order to not have to re-write "a million lines of code", I had to create an ajax-endpoint that gave me the http-cookie's content as JSON and rewrite readCookie to use SYNCHRONOUS ajax requests (instead of reading the cookie), because the rest of the horrible code expects readCookie to be synchronous at these million places, because reading a cookie is synchronous.
The problem now is, I get a lot of
Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user's experience. For more help, check https://xhr.spec.whatwg.org/.
which spams the debug console, let alone the possibility someone decides to remove this functionality.
I am therefore looking into the new ES async/await keywords, to see if that could help somehow in making a asynchronous ajax-request synchronously (i know I have to use wrappers for IE 11).
So far, I read these pages
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*
but it looks like all the new async stuff seems to only cater to the problem of writing asynchronous code easier, not enabling interop between asynchronous and existing synchronous code. Using the information I read, I can now await the result of an asynchronous ajax-call like it was synchronous, but the problem is - await is only allowed in async-methods... Which means even if I can await the result like it was synchronous, the getCookie method would still have to be async, which makes all the stuff appear to be completely pointless (unless your entire code would-be async, which it certainly isn't when you don't start from scratch)...
I can't seem to find any information on how to interop between synchronous and asynchronous code.
For example, in C#, I can call an async-method from a synchronous context with .Result, e.g.
AsyncContext.RunTask(MyAsyncMethod).Result;
or easier but less deadlock-safe like
MyAsyncMethod(args).Result;
Is there any way to achieve the same in JavaScript ?
It seems to make little sense to spread async around, when the rest of the codebase is synchronous, without any possibility of interop... Is there really still no way to achieve this in JavaScript in 2017 AD ?
I emphasize again:
I know how I can make a synchronous ajax-call, and I know how to use async ajax calls with callbacks and/or promises.
But what I'm unable to figure out is how to synchronize an async-ajax-call (no callback) so it can be used from code that expects to be run synchronously (in "a million places") !
This is what I have tried so far:
(Note that whether I use loadQuote
or main
, the text "Ron once said" still appears first in the debug-console, which should not be the case if the asynchronous ajax-call had been resolved synchronously)
<!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>
await
orthen
). Then replace the "million" synchronousreadCookie
calls with a synchronousreadCookieFromCache
call. – Withdraw