如何在同步函数中等待JavaScript中的异步调用? [英] How to await an async call in JavaScript in a synchronous function?

查看:99
本文介绍了如何在同步函数中等待JavaScript中的异步调用?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

百万个地方

因此,为了不必重写一百万行代码,我必须创建一个ajax-endpoint,它给了我http-cookie的内容作为JSON并重写
readCookie使用 SYNCHRONOUS ajax请求(而不是读取cookie),因为其余的可怕代码期望
readCookie在这些百万位置同步,因为读取cookie是同步的。

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


同步XMLHttpRequest on主线程因为
而被弃用,这对最终用户的体验产生了不利影响。如需更多帮助,
请查看 https://xhr.spec.whatwg.org/

它会调试调试控制台,更不用说有人决定删除此功能的可能性。

which spams the debug console, let alone the possibility someone decides to remove this functionality.

因此,我正在研究新的ES async / await关键字,看看这是否有助于以某种方式同步制作异步ajax请求(我知道我必须使用IE 11的包装器。

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).

到目前为止,我读过这些页面

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 /语句/功能 *

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*

但看起来所有新的异步内容似乎只能解决编写异步代码更容易的问题,不启用异步和现有同步代码之间的互操作。使用我读过的信息,我现在可以等待异步ajax调用的结果,就像它是同步的,
但问题是 - await只允许在async-methods中使用...
这意味着什么如果我可以等待结果,就像它是同步的那样,getCookie方法仍然必须是异步的,这使得所有的东西看起来都是毫无意义的(除非你的整个代码都是异步的,当你不穿时它肯定不是从头开始)...

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.

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

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;

有没有办法在JavaScript中实现相同的目标?

Is there any way to achieve the same in JavaScript ?

当代码库的其余部分同步时,传播异步似乎毫无意义,没有任何互操作的可能性......
真的有吗仍然无法在2017年的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 ?

我再次强调

我知道我如何制作同步ajax -call,我知道如何使用带有回调和/或承诺的异步ajax调用。


但是我无法弄清楚的是如何同步async-ajax-call (无回调)以便可以从代码中使用它期待同步(在一百万个地方)!

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

这是我到目前为止所尝试的:

(请注意,我是否使用 loadQuote main ,文本Ron曾经说过仍然首先出现在调试控制台中,如果异步 ajax调用已经出现,则不应该出现这种情况已解决同步

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>


推荐答案


但问题是 - 只允许在async-methods中使用await。

but the problem is - await is only allowed in async-methods.

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

Exactly, and no, there's no workaround for that. JavaScript's run-to-completion semantics demand that synchronous functions complete before any pending asynchronous action (such as the callback to an XHR handler for an async XHR call) can run.

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

The way JavaScript runs on a given thread is that it processes a queue of jobs1:


  1. 选择下一个待处理作业

  2. 同步执行该作业的代码

  3. 仅当该作业完成后,返回步骤1拿起下一份工作

(比这复杂一点,它有两个级别,但那不是与此特定问题相关。)

XHR完成等等是在队列中安排的作业。无法暂停作业,从队列中运行另一个作业,然后选择暂停的作业。 async / await 为处理异步操作提供了极其简单的语法,但它们没有更改作业队列的性质。

XHR completions and such are jobs that get scheduled in the queue. There is no way to pause a job, run another job from the queue, and the pick up the paused job. async/await provide dramatically simpler syntax for handling asynchronous operations, but they don't change the nature of the job queue.

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

The only solution I see for your situation is to go async all the way to the top level. This may not be as complicated as you might think (or maybe it will be). In many cases it's adding async in front of function on a lot of functions. However, making those functions asynchronous is likely to have significant knock-on effects (for instance, something that was synchronous in an event handler becoming asynchronous changes the timing of what happens in relation to the UI).

例如,考虑这个同步代码:

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>

现在我们想让make doThat 异步(注意:仅适用于最近的浏览器支持 async / 等待,就像Chrome一样;遗憾的是Stack Snippet的Babel配置不包含它们,所以我们可以不使用该选项):

Now we want to make make doThat async (note: will only work on a recent browser supporting async/await, like Chrome; sadly Stack Snippet's Babel config doesn't include them, so we can't use that option):

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 (自此处理程序不能是异步的)。但是,当然,这会改变与处理程序相关的工作时间。 (当然,我们可能应该更新处理程序以捕获来自promise`doSomething()返回的错误。)

The key thing there is we went async as soon as we could, in doSomething (since handler can't be async). But of course, that changes the timing of the work in relation to the handler. (Of course, we probably should have updated handler to catch errors from the promise `doSomething() returns.)

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

1 That's the JavaScript spec terminology. The HTML5 spec (which also touches on this) calls them "tasks" instead of "jobs".

这篇关于如何在同步函数中等待JavaScript中的异步调用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆