web-dev-qa-db-ja.com

同期関数のJavaScriptで非同期呼び出しを待つ方法は?

最近、Webアプリケーション(作成しなかったもの)のセキュリティ問題を修正する必要がありました。セキュリティ上の問題は、非httpのみのcookieを使用していたことです。したがって、セッションCookieをhttpのみに設定する必要がありました。つまり、JavaScriptからCookieの値を読み取る(および設定する)ことができなくなりました。これまでのところ継ぎ目なく簡単です。

より深い問題は、使用したWebアプリケーション

JSON.parse(readCookie(cookieName)).some_value

100万の場所

したがって、「100万行のコード」を書き換える必要がないように、http-cookieのコンテンツをJSONとして与えるajax-endpointを作成し、readCookieを書き換えて使用する必要がありました [〜#〜]同期[〜#〜]ajaxリクエスト(Cookieを読み取るのではなく)。残りの恐ろしいコードは、readCookieがこれらの100万の場所で同期することを期待しているため、 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 ADのJavaScriptでこれを実現する方法はまだありませんか?

もう一度強調します
知っています同期ajax呼び出しを行う方法、および知っていますコールバックやプロミスで非同期ajax呼び出しを使用する方法。
しかし、私が理解できないのは、async-ajax-call(コールバックなし)を同期する方法です。実行されると予想されるコードから使用同期的に(「100万の場所」で)!

これは私がこれまでに試したことです:
(使用するかどうかに注意してくださいloadQuoteまたはmain、テキスト「Ron once said」は引き続きデバッグコンソールに最初に表示されますが、asynchronousajax-callが解決されました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>
11
Stefan Steiger

しかし問題は-awaitは非同期メソッドでのみ許可されます。

正確に、そしていいえ、そのための回避策はありません。 JavaScriptのrun-to-completionセマンティクスdemand保留中の非同期アクション(非同期XHR呼び出しのXHRハンドラーへのコールバックなど)が実行される前に同期関数が完了すること。

JavaScriptが特定のスレッドで実行される方法は、ジョブのキューを処理することです1

  1. 次の保留中のジョブをピックアップ
  2. そのジョブのコードを同期的に実行する
  3. そのジョブが完了した場合にのみ、ステップ1に戻って次のジョブを選択します

(それよりも少し複雑ですが、2つのレベルがありますが、それはこの特定の質問には関係ありません。)

XHR完了などは、キューでスケジュールされるジョブです。ジョブを一時停止し、キューから別のジョブを実行し、一時停止したジョブを取得する方法はありません。 async/awaitは、非同期操作を処理するための劇的に単純なsyntaxを提供しますが、それらは、ジョブキュー。

あなたの状況について私が見る唯一の解決策は、トップレベルまでずっと非同期にすることです。これは、あなたが考えるほど複雑ではないかもしれません(または多分そうなるでしょう)。多くの場合、多くの関数で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を非同期にしたいとします(note:Chromeのように、async/awaitをサポートする最近のブラウザでのみ機能します。残念ながらStackスニペットの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は非同期にできないため)。しかしもちろん、それはハンドラーに関連して作業のタイミングを変更します。 (もちろん、 `_Something()が返すという約束からのエラーをキャッチするためにhandlerを更新する必要があるでしょう。)


1 これがJavaScript仕様の用語です。 HTML5仕様(これについても触れています)では、「ジョブ」ではなく「タスク」と呼んでいます。

7
T.J. Crowder

アプローチに問題があります。最初に、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からデータを取得します。他のことを待つ必要はありません。

3
motanelu

短い答え:いいえ、C#でわかっているように、非同期コードをJSで同期的に実行する方法はありません。すべてを非同期にすることは可能な解決策です。

ただし、サーバー側も制御しているため、別の提案があります(ハックのビット):要求のメタデータとして必要な情報(Cookieコンテンツ)を送信します。ページリクエストのHTMLメタタグまたはXHRリクエストのHTTPレスポンスヘッダーとして、どこかに保存します。

1
Lucero