How to wait for an asynchronous call in JavaScript in a synchronous function?

Recently, I had to fix security issues in a web application (which I did not create). A security issue was the use of cookies other than http. So I had to set only the http-only session-cookie, which means you can no longer read (and set) the cookie value from javascript. So far it has been easy.

The deeper problem was that the web application was using

JSON.parse(readCookie(cookieName)).some_value 

per million places .

So, in order not to rewrite the “million lines of code”, I had to create an ajax-end point that gave me the http cookie content as JSON and rewrite readCookie to use SYNCHRONOUS ajax requests (instead of reading the cookie), because the rest of the awful code expects readCookie will be synchronous in these millions of places because reading a cookie is synchronous.

The problem is that I get a lot

The synchronous XMLHttpRequest in the main thread is deprecated due to its detrimental effects on the end user. For more help, check out https://xhr.spec.whatwg.org/ .

which spam the debug console, not to mention that someone decides to remove this functionality.

Therefore, I am studying the new async / await ES keywords to find out if this can somehow help in creating an asynchronous ajax request synchronously (I know that I need to use shells for IE 11).

So far i have 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 seems that all new asynchronous things, apparently, only make the task of writing asynchronous code easier, and do not allow interaction between asynchronous and existing synchronous code. Using the information I read, now I can wait for the result of an asynchronous ajax call, as if it were synchronous, but the problem is that - waiting is allowed only in asynchronous methods ... This means that even if I can wait for the result , as if it were synchronous, the getCookie method still had to be asynchronous, which makes all things completely pointless (unless all your code is asynchronous, which is certainly not when you are not starting from scratch) ...

I cannot find any information on how to interact between synchronous and asynchronous code.

For example, in C #, I can call an asynchronous method from a synchronous context with .Result, for example.

  AsyncContext.RunTask(MyAsyncMethod).Result; 

or simpler but less secure like

 MyAsyncMethod(args).Result; 

Is there a way to do the same in JavaScript?

It doesn't seem to make sense to propagate the asynchronous move when the rest of the code is synchronous, without any interoperability ... Is there really still no way to achieve this in JavaScript in 2017?

I emphasize again :
I know how I can make a synchronous ajax call, and I know how to use async async calls with callbacks and / or promises.

But I can’t understand how to synchronize async-ajax-call (without a callback) , so it can be used from code that is waiting to be executed synchronously (in "millions of places")!

This is what I have tried so far:
(Note that if 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 ajax call is asynchronous allowed 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> 
+4
source share
3 answers

but the problem is that - waiting is allowed only in asynchronous methods.

Exactly, and no, there is no workaround for this. Segmentation of JavaScript execution-completion requires that synchronous functions be executed before any pending asynchronous action (for example, a callback to the XHR handler to invoke async XHR) can be executed.

The way JavaScript works in this thread is that it processes job queue 1 :

  • Select the next unfinished task
  • Run code for this job synchronously
  • Only after completing this work, return to step 1 to select the next task.

(This is a little more complicated, there are two levels, but this does not apply to this specific issue.)

Upon completion of XHR, etc. These are tasks that are scheduled in the queue. It is not possible to pause work, start another task from the queue, and pick up paused work. async / await provide a significantly simpler syntax for handling asynchronous operations, but they do not change the nature of the job queue.

The only solution I see for your situation is asynchronous transition to the upper level. It may not be as difficult as you think (or maybe it will be). In many cases, it adds async before the function for many functions. However, if these functions are not synchronous, they are likely to have significant detonation effects (for example, something that was synchronous in the event handler becoming asynchronous changes the time that occurs in connection with the user interface).

For example, consider this synchronous code:

 var btn = document.getElementById("btn"); btn.addEventListener("click", handler, false); function handler(e) { console.log("handler triggered"); doSomething(); console.log("handler done"); } function doSomething() { doThis(); doThat(); doTheOther(); } function doThis() { console.log("doThis - start & end"); } function doThat() { console.log("doThat - start"); // do something that takes a while var stop = Date.now() + 1000; while (Date.now() < stop) { // wait } console.log("doThat - end"); } function doTheOther() { console.log("doThat - start & end"); } 
 .as-console.wrapper { max-height: 80% !important; } 
 <input type="button" id="btn" value="Click Me"> <p id="text"></p> 

Now we want to make make doThat async ( note ): it will work only in the latest browser supporting async / await , for example Chrome; Unfortunately, the Stack Snippet Babel config does not enable them, so we will not be able to use this parameter):

 var btn = document.getElementById("btn"); btn.addEventListener("click", handler, false); // handler can't be async function handler(e) { console.log("handler triggered"); doSomething(); console.log("handler done"); } // doSomething can be async function doSomething() { doThis(); await doThat(); doTheOther(); } function doThis() { console.log("doThis - start & end"); } // make doThat async async function doThat() { console.log("doThat - start"); // simulate beginning async operation with setTimeout return new Promise(resolve => { setTimeout(() => { // do something that takes a while var stop = Date.now() + 1000; while (Date.now() < stop) { // wait } console.log("doThat - end (async)"); }, 0); }); } function doTheOther() { console.log("doThat - start & end"); } 
 .as-console.wrapper { max-height: 80% !important; } 
 <input type="button" id="btn" value="Click Me"> <p id="text"></p> 

The main thing is that we went to async as soon as we could, to doSomething (since the handler cannot be asynchronous). But of course, this changes the run time in relation to the handler. (Of course, we probably should have updated the handler to catch errors from the promise `doSomething () returns.)


1 This is a JavaScript JavaScript specification. The HTML5 specification (which also addresses this) calls them "tasks" instead of "tasks."

+3
source

Short answer: there is no way to make synchronous asynchronous code run in JS, as you know, from C #. A possible solution is to create an asynchronous solution.

However, since you also control the server, I have another suggestion (hack bit): send the necessary information (the contents of the cookie) as request metadata, for example. as an HTML meta tag for page requests or an HTTP response header for XHR requests and store it somewhere.

+1
source

There is a problem with your approach. First, to complete the operation with the await code for async , it must be wrapped in an async function.

For instance:

 async function asyncExample () { try { const response = await myPromise() // the code here will wait for the // promise to fullfil } catch (error) { // the code here will execute if the promise fails } } function nonAsyncExample () { asyncExample () console.log('this will not wait for the async to finish') // as it not wrapped in an async function itself } 

You can try to declare the autorun() function as async , but this can lead to additional complications.

My suggestion is, if your JS application has an entry point, it is triggered by the onload , try making your ajax call to that point, and then save it locally in a variable and request it from there.

For example, if your code looks like this:

 function init () { // perform initialisations here } document.addEventListener("DOMContentLoaded", init) 

change the value

 document.addEventListener("DOMContentLoaded", function () { getAjaxConfig().then(function (response) { window.cookieStash = response init() } }) 

and get data from cookieStash in the rest of the application. You do not need to wait for something else.

+1
source

Source: https://habr.com/ru/post/1263657/


All Articles