如何在同步函数中等待JavaScript中的异步调用?

时间:2017-02-01 09:51:18

标签: javascript ajax asynchronous async-await synchronize

我最近不得不纠正网络应用程序中的安全问题(我没有创建)。 安全问题是,它使用的是非http-only cookie。 所以我必须设置会话cookie的http-only,这意味着你不能再从javascript读取(和设置)cookie的值。 到目前为止这么容易缝合。

更深层次的问题是,使用了网络应用程序

JSON.parse(readCookie(cookieName)).some_value

在一百万个地方

因此,为了不必重新编写一百万行代码",我必须创建一个ajax-endpoint,它将http-cookie的内容作为JSON重写并重写 readCookie使用 SYNCHRONOUS 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方法仍然必须是异步的,这使得所有的东西看起来都是毫无意义的(除非你的整个代码都是异步的,它肯定不是&#39 ;当你不从头开始时......)

我似乎无法找到有关如何在同步和异步代码之间进行互操作的任何信息。

例如,在C#中,我可以使用.Result从同步上下文中调用async-method,例如

 AsyncContext.RunTask(MyAsyncMethod).Result;

或者更容易但更少死锁安全,如

MyAsyncMethod(args).Result;

有没有办法在JavaScript中实现相同的功能?

当代码库的其余部分是同步的时,传播异步似乎毫无意义,没有任何互操作的可能性...... 在公元2017年,在JavaScript中真的还没有办法实现这一目标吗?

我再次强调
我知道我如何进行同步ajax调用,我知道如何使用带有回调和/或承诺的异步ajax调用。
但我无法弄清楚的是如何同步async-ajax-call (无回调),以便可以从期望同时运行的代码中使用 strong>(在"一百万个地方")!

这是我到目前为止所尝试的:
(请注意,无论我使用 loadQuote 还是 main ,文字" Ron曾说过&#34 ; 仍然首先出现在调试控制台中,如果异步 ajax调用已经解析同步 p>

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

3 个答案:

答案 0 :(得分:7)

  

但问题是 - 等待仅允许在async-methods中使用。

确切地说,没有,没有解决方法。 JavaScript的运行完成语义 demand 同步函数在任何挂起的异步操作(例如异步XHR调用的XHR处理程序的回调)之前完成,可以运行。

JavaScript在给定线程上运行的方式是它处理作业队列 1

  1. 选择下一个待处理的作业
  2. 同步执行该作业的代码
  3. 只有当该工作完成后,请返回步骤1以获取下一份工作
  4. (它有点复杂,有两个级别,但这与这个特定问题无关。)

    XHR完成等等是在队列中调度的作业。无法暂停作业,从队列中运行另一个作业,然后选择暂停的作业。 async / await提供了非常简单的语法来处理异步操作,但它们不会改变作业队列的性质。

    我看到的唯一解决方案就是一直异步到顶级。这可能没有您想象的那么复杂(或者可能会如此)。在许多情况下,它会在async前面在很多函数上添加function。但是,使这些函数异步可能会产生明显的连锁效应(例如,在事件处理程序变为异步时同步的某些东西会改变与UI相关的时间)。

    例如,请考虑此同步代码:

    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>

    现在我们想让make doThat异步(注意:仅适用于最近支持async / await的浏览器,例如Chrome;遗憾的是Stack Snippet的Babel配置不包含它们,因此我们不能使用该选项):

    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>

    关键是我们尽可能地在doSomething中进行异步(因为handler不能异步)。但是,当然,这会改变与处理程序相关的工作时间。 (当然,我们可能应该更新handler来捕获promise`doSomething()返回的错误。)

    1 这是JavaScript规范术语。 HTML5规范(也涉及到这一点)将它们称为“任务”而不是“作业”。

答案 1 :(得分:3)

您的方法存在问题。首先,对于要完成await操作的async部分代码,必须将其自身包装在async函数中。

例如:

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's not wrapped in an async function itself
}

您可以尝试将autorun()函数声明为async,但这可能会导致其他并发症。

我的建议是,如果您的JS应用程序有一个入口点,它由onload事件触发,请在此之前尝试进行ajax调用,然后将其本地存储在变量中并查询它从那里。

例如,如果您的代码如下:

function init () {
    // perform initialisations here
}

document.addEventListener("DOMContentLoaded", init)

将其更改为

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

并从应用程序其余部分的cookieStash获取数据。你不需要等待其他任何事情。

答案 2 :(得分:1)

简短的回答:没有办法让你的异步代码在JS中运行同步,就像你从C#中知道的那样。使一切都异步是一种可能的解决方案。

但是,由于您还控制服务器端,我还有另一个建议(黑客攻击):将所需信息(cookie内容)作为请求的元数据发送,例如:作为页面请求的HTML元标记或XHR请求的HTTP响应标头,并将其存储在某处。