【问题标题】:What's C#'s await equivalent in javascript/jquery?javascript/jquery 中 C# 的 await 等价物是什么?
【发布时间】:2014-04-24 08:15:38
【问题描述】:

我正在使用一个名为 bootbox 的 jQuery 库

bootbox.dialog({
    title: "Group",
    buttons: {
        success: {
            label: "OK",
            className: "btn-success",
            callback: function () {
                postForm();
            }
        }
    }
});

function postForm() {        
    $.ajax({
        type: "POST",
        url: $("#add-group").val(),
        data: $("#form").serialize(),
        success: function (data) {
            return true;
        },
        error: function (XMLHttpRequest, textStatus, errorThrown) {
            return false;
        }
    });
}

当我单击“确定”按钮时,除非我返回如下错误值:

callback: function () {
    return false;
}

对话框将关闭。

在回调函数中,我正在调用postForm,这是一个对我的服务器端进行AJAX 调用以执行某些操作的函数。根据该操作的结果,我希望对话框保持打开状态。

但是因为它是一个 AJAX 调用,并且结果需要一段时间才能回来,所以无论我的结果如何,对话框都会在 postForm() 之后立即关闭。

如何告诉 javascript 等待 ajax 调用的结果?

如果我能做这样的事情那就太好了:

callback: function () {
    var result = await postForm();
    return result;
}

【问题讨论】:

  • Javascript 不是多线程的,因此无法等待。只有同步操作(代码按顺序均匀执行)和异步操作,操作完成后调用回调函数。一种解决方法是 jquery promise(),实际上是间接回调。
  • 所以我可以将 postForm() 包装在 promise() 中?
  • 我想你可以试试$.when in postForm()。我不确定,但它应该可以工作

标签: javascript async-await bootbox


【解决方案1】:

我认为Promises 正是您要问的。

.promise()

返回一个 Promise 对象以观察绑定到集合的特定类型的所有操作(无论是否排队)都已完成。例如

var div = $( "<div>" );

div.promise().done(function( arg1 ) {
  // Will fire right away and alert "true"
  alert( this === div && arg1 === div );
});

更多信息请参考:https://api.jquery.com/promise/

Deferred Promise 更接近异步行为:

deferred.promise()

deferred.promise() 方法允许异步函数防止其他代码干扰其内部请求的进度或状态。 Promise 仅公开附加附加处理程序或确定状态所需的延迟方法(then、done、fail、always、pipe、progress 和 state),但不公开更改状态(resolve、reject、notify、resolveWith、rejectWith 和 notifyWith)。

如果提供了目标,deferred.promise() 会将方法附加到它上面,然后返回这个对象而不是创建一个新对象。这对于将 Promise 行为附加到已存在的对象很有用。

如果您要创建 Deferred,请保留对 Deferred 的引用,以便在某个时候解决或拒绝它。通过deferred.promise() 仅返回 Promise 对象,以便其他代码可以注册回调或检查当前状态。

例子:

function asyncEvent() {
  var dfd = new jQuery.Deferred();

  // Resolve after a random interval
  setTimeout(function() {
    dfd.resolve( "hurray" );
  }, Math.floor( 400 + Math.random() * 2000 ) );

  // Reject after a random interval
  setTimeout(function() {
    dfd.reject( "sorry" );
  }, Math.floor( 400 + Math.random() * 2000 ) );

  // Show a "working..." message every half-second
  setTimeout(function working() {
    if ( dfd.state() === "pending" ) {
      dfd.notify( "working... " );
      setTimeout( working, 500 );
    }
  }, 1 );

  // Return the Promise so caller can't change the Deferred
  return dfd.promise();
}

// Attach a done, fail, and progress handler for the asyncEvent
$.when( asyncEvent() ).then(
  function( status ) {
    alert( status + ", things are going well" );
  },
  function( status ) {
    alert( status + ", you fail this time" );
  },
  function( status ) {
    $( "body" ).append( status );
  }
);

有关详细信息,请参阅延迟对象的文档:http://api.jquery.com/category/deferred-object/

jQuery.when()

提供一种基于一个或多个对象执行回调函数的方法,通常是表示异步事件的延迟对象。示例:

$.when( $.ajax( "test.aspx" ) ).then(function( data, textStatus, jqXHR ) {
  alert( jqXHR.status ); // Alerts 200
});

【讨论】:

  • 可以扩展您的答案以反映它在我的场景中的样子吗?
  • 我不确定bootbox,似乎单击“确定”具有在该库中隐藏对话框的默认操作。在简单对话框中,我通过仅在条件下隐藏对话框来完成相同的操作。
  • 如果我在回调中返回false,它不会返回关闭对话框
  • 我已经更新了我的 OP。它似乎没有“等待”。我在回调中正确返回真/假,但对话框无论如何都会关闭
【解决方案2】:

JavaScript(当前)没有与async/await 等效的语言。有可用于 JavaScript 的 various promise libraries,它为您提供大致相当于 Task 类型。这是在原始回调之上的一个很好的步骤,但是除了最简单的场景之外,您仍然会遇到尴尬的嵌套或回调意大利面。

JavaScript ECMAScript 6(“Harmony”)预计将包含generators。 ES6 有望在今年晚些时候正式发布,但在此之后可能需要一段时间才能安全地假设用户的浏览器支持生成器。

通过combining generators with promises,您可以实现真正的async/await 等效项。

【讨论】:

    【解决方案3】:

    随着 ES2017 的出现,这个问题的答案是 async/await。
    这是“回调地狱”的 JS 解决方案。 Promise 等价于 System.Threading.Tasks.Task。 您可以在异步函数中等待 Promise。
    与 C# 不同,无法在同步函数中调用异步函数。
    因此,您可以在 async 函数中await 一个 Promise,并且只能在 async 函数中。

    async function foo()
    {
         return 123;
    }
    
    let result = await foo();
    console.log(result)
    

    您可以使用 TypeScript 或 babel 将 async/await 转译回 ES5(当 IE11 存在时)。
    IE11 有一个 promise polyfill。
    有关详细信息,请参阅 ECMA-draft 262 或 MDN。

    承诺的一个很好的例子是 FETCH api。
    fetch-API 适用于 async/await ajax 请求。
    为什么 ?因为如果你必须promisify XmlHttpRequest,它看起来像这样:

    <!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 ajaxGet(url, data)
            {
                var result;
    
                return new Promise(function (resolve, reject)
                    {
    
                        ajax.get(url, data, function (bError, strMessage, iStatus)
                            {
    
                                // console.log("args:", arguments);
    
                                // console.log("Error:", bError);
                                // console.log("Message:", strMessage);
                                // console.log("Status:", iStatus);
    
                                result = bError;
                                resolve(result);
                            }
                            ,true
                        );
    
                    }
                );
    
            }
    
    
            async function main()
            {
                var ajaxResult = await ajaxGet("./ajax/json.ashx");
                console.log("ajaxResult: ", ajaxResult);
            }
    
            async function lol() 
            { 
                var res = null;
    
                var myPromise = new Promise(function (resolve, reject)
                {
                    // Standard AJAX request setup and load.
                    var request = new XMLHttpRequest();
    
                    // Request a user's comment from our fake blog.
                    request.open('GET', 'https://localhost:57566/ajax/json.ashx');
    
                    /*
                    // Set function to call when resource is loaded.
                    // Onload same as onreadystatechange - onload added with XHR2
                    request.onload = function ()
                    {
                        // internal server error/404
                        if (request.status === 200)
                        {
                            res = request.response;
                            // console.log(request.response);
                            console.log("onload- resolving promise");
                            resolve(request.response);
                        } else
                        {
                            console.log("onload- rejectinv promise");
                            reject('Page loaded, but status not OK.');
                        }
                    };
                    */
    
    
                    request.onreadystatechange = function ()
                    {
                        console.log("readystate:", request.readyState);
                        console.log("status:", request.status)
    
                        if (request.readyState != 4) return;
    
                        // XMLHttpRequest.DONE = 200, 0=cancelled 304 = redirect
                        //if (!(request.status != 200 && request.status != 304 && request.status != 0))
                        if (request.status === 200)
                        {
                            console.log("successy")
                            resolve(request.responseText); // Success 
                            return;
                        }
    
                        if (request.status != 200 && request.status != 0 && request.status != 304)
                        {
                            console.log('HTTP error ' + request.status);
                            // reject('Page loaded, but status not OK.');
                            reject(new Error("Server error - Status NOK", "filename", "linenum666")); // Error 
                            return;
                        }
    
                        if (request.status === 0)
                        {
                            console.log("cancelled:", request)
                            //resolve(null); // Cancelled, HTTPS protocol error
                            return;
                        }
    
                        reject(new Error("Strange error", "filename", "linenum666")); // Some Error 
                    };
    
                    // Set function to call when loading fails.
                    request.onerror = function ()
                    {
                        // Cannot connect 
                        console.log("OMG OnError");
                        // reject('Aww, didn\'t work at all. Network connectivity issue.');
                        reject(new Error("Aww, didn\'t work at all. Network connectivity issue.", "filename", "linenum666")); // Some Error 
    
                    };
    
    
                    if (!navigator.onLine)
                    {
                        console.log("No internet connection");
                        reject("No internet connection");
                    }
                    else
                    {
                        try
                        {
                            request.send();
                        }
                        catch (ex)
                        {
                            console.log("send", ex.message, ex);
                        }
                    }
    
                });
    
                return myPromise;
            }
    
    
    
            async function autorun()
            {
                console.clear();
                // await main();
    
                try
                {
                    var resp = await lol();
                    console.log("resp:", resp);
                }
                catch (ex)
                {
                    console.log("foo", ex.message, ex);
                }
    
    
    
                console.log("I am here !");
            }
    
            if (document.addEventListener) document.addEventListener("DOMContentLoaded", autorun, false);
            else if (document.attachEvent) document.attachEvent("onreadystatechange", autorun);
            else window.onload = autorun;
        </script>
    
    </body>
    </html>
    

    【讨论】:

      【解决方案4】:

      你不能。 JS 中没有 await 等价物。

      您必须通过在调用 postForm 时返回 false 来模拟它,然后在执行来自 AJAX 调用的回调函数时关闭对话框。

      编辑/更新:从 ES2017 开始,有 async/await 支持 - 虽然我不知道它是否可以与 jQuery 一起使用。

      【讨论】:

      • 我认为我们可以在 jQuery 中使用 promise 进行等效模拟。
      • 是的,看起来你可以,这或多或少是我所说的 - 没有语言支持,但 jQuery 显然有你需要模拟它的东西。
      • 我同意弗雷德里克的观点;你不能在 JS 中真正模拟相同的行为。在 C# 中,您可以将变量设置为调用的等待值,例如var name = await getNameAsync();,但是无论你如何切片,你都必须求助于在 JS 中使用回调。没什么大不了的,但是如果您有多个依赖于另一个调用的异步结果的异步函数,这可能会导致深度嵌套调用。在 JS 中,相同的代码是 var name; getName.then(newName =&gt; name = newName);
      【解决方案5】:

      请参阅Stefan's answer 现在使用 ES2015 async/await。


      原答案

      您可以考虑asyncawait,它可以有效地让您编写如下代码

      var foo = async (function() {
          var resultA = await (firstAsyncCall());
          var resultB = await (secondAsyncCallUsing(resultA));
          var resultC = await (thirdAsyncCallUsing(resultB));
          return doSomethingWith(resultC);
      });
      

      而不是下面的

      function foo2(callback) {
          firstAsyncCall(function (err, resultA) {
              if (err) { callback(err); return; }
              secondAsyncCallUsing(resultA, function (err, resultB) {
                  if (err) { callback(err); return; }
                  thirdAsyncCallUsing(resultB, function (err, resultC) {
                      if (err) {
                          callback(err);
                      } else {
                          callback(null, doSomethingWith(resultC));
                      }
                  });
      
              });
          });
      }
      

      【讨论】:

        【解决方案6】:

        您可以将 ES6 生成器和 yield 功能与 Google Traceur Compiler 一起使用,正如我在 here 中描述的那样。

        Traceur 团队有some feedback 表示它是生产质量的工具。它还有experimental support 对应async/await。

        【讨论】:

          【解决方案7】:

          虽然这不能回答 “C# 在 JavaScript 中的 await 的等价物是什么?” 问题,但问题中的代码可以很容易地工作。 bootbox.dialog 函数返回一个对象,因此您可以像这样调整显示的代码:

          var dialog = bootbox.dialog({
              title: "Group",
              buttons: {
                  success: {
                      label: "OK",
                      className: "btn-success",
                      callback: function () {
                          postForm();
                          return false; // add this return here
                      }
                  }
              }
          });
          

          然后将ajax调用调整为:

          function postForm() {        
              $.ajax({
                  type: "POST",
                  url: $("#add-group").val(),
                  data: $("#form").serialize(),
                  success: function (data) {
                      // add this call to the underlying Bootstrap modal object
                      dialog.modal('hide'); 
                  },
                  error: function (XMLHttpRequest, textStatus, errorThrown) {
                      // Maybe inject an error message into the dialog?
                  }
              });
          }
          

          【讨论】:

            猜你喜欢
            • 2011-07-07
            • 1970-01-01
            • 1970-01-01
            • 2014-05-08
            • 1970-01-01
            • 2011-03-25
            • 1970-01-01
            • 2011-10-14
            • 1970-01-01
            相关资源
            最近更新 更多