怎么解决js异步方法执行顺序

更新日期: 2021-10-09阅读: 891

问题描述

一个foo发出异步请求的函数,如何从返回响应/结果?尝试从回调中返回值,并将结果分配给函数内的局部变量并返回该变量,但这些方法都没有真正返回响应(它们都返回undefined或变量的初始值)。

使用 jqueryajax函数的示例:

function foo() {
    var result;
    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result; // It always returns `undefined`
}

使用 Node.js 的示例:

function foo() {
    var result;

    fs.readFile("path/to/file", function(err, data) {
        result = data;
        // return data; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

使用then承诺块的示例:

function foo() {
    var result;

    fetch(url).then(function(response) {
        result = response;
        // return response; // <- I tried that one as well
    });

    return result; // It always returns `undefined`
}

在Ajax中代表是异步。这意味着发送请求(或者更确切地说是接收响应)已经脱离了正常的执行流程。在您的示例中,$.ajax立即返回,并且在return result;您作为success回调传递的函数甚至被调用之前执行下一条语句。这是一个类比,希望可以更清楚地区分同步流和异步流之间的区别:


同步

想象一下,你给朋友打电话,让他帮你查点东西。虽然这可能需要一段时间,但您会等待电话并凝视太空,直到您的朋友给您所需的答案。

当您进行包含“正常”代码的函数调用时,也会发生同样的情况:

function findItem() {
    var item;
    while(item_not_found) {
        // search
    }
    return item;
}

var item = findItem();

// Do something with item
doSomethingElse();

尽管findItem可能需要很长时间才能执行,但之后的任何代码var item = findItem();都必须等到函数返回结果。


异步

出于同样的原因,您再次致电您的朋友。但这一次你告诉他你有急事,他应该用你的手机给你回电。你挂断电话,离开家,做你计划做的任何事情。一旦你的朋友给你回电,你就在处理他给你的信息。

这正是您执行 Ajax 请求时发生的情况。

findItem(function(item) {
    // Do something with the item
});
doSomethingElse();

不是等待响应,而是立即继续执行并执行 Ajax 调用之后的语句。为了得到响应,最终,你提供了一次收到答复要调用的函数,一个回调(注意些什么呢?回电?)。在调用回调之前执行该调用之后的任何语句。


解决方案

拥抱 JavaScript 的异步特性!虽然某些异步操作提供同步对应物(“Ajax”也是如此),但通常不鼓励使用它们,尤其是在浏览器上下文中。

你问为什么不好?

JavaScript 在浏览器的 UI 线程中运行,任何长时间运行的进程都会锁定 UI,使其无响应。此外,JavaScript 的执行时间有上限,浏览器会询问用户是否继续执行。

所有这些都会导致非常糟糕的用户体验。用户将无法判断是否一切正常。此外,对于连接速度较慢的用户,效果会更差。

在下文中,我们将研究三种不同的解决方案,它们都建立在彼此之上:

  • Promises withasync/await(ES2017+,如果您使用转译器或再生器,则在旧浏览器中可用)
  • 回调(在节点中流行)
  • Promises withthen()(ES2015+,如果您使用众多 Promise 库之一,则可在较旧的浏览器中使用)

所有这三个都在当前浏览器和节点 7+ 中可用。


ES2017+:承诺 async/await

2017 年发布的 ECMAScript 版本引入了对异步函数的语法级支持。的帮助下async和await,你可以写在“同步式”异步的。代码仍然是异步的,但更容易阅读/理解。

async/await建立在承诺之上:一个async函数总是返回一个承诺。await“解开”一个promise 并且要么导致promise 被解决的值,要么在promise 被拒绝时抛出一个错误。

重要提示:您只能await在async函数内部使用。目前,await尚不支持顶级,因此您可能需要创建一个异步 IIFE(立即调用函数表达式)来启动async上下文。

你可以在 MDN 上阅读更多关于async和await。

这是一个详细说明上述延迟函数的示例findItem():

// Using 'superagent' which will return a promise.
var superagent = require('superagent')

// This is isn't declared as `async` because it already returns a promise
function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

async function getAllBooks() {
  try {
    // GET a list of book IDs of the current user
    var bookIDs = await superagent.get('/user/books');
    // wait for 3 seconds (just for the sake of this example)
    await delay();
    // GET information about each book
    return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
  } catch(error) {
    // If any of the awaited promises was rejected, this catch block
    // would catch the rejection reason
    return null;
  }
}

// Start an IIFE to use `await` at the top level
(async function(){
  let books = await getAllBooks();
  console.log(books);
})();

当前浏览器和节点版本支持async/await. 您还可以通过在regenerator(或使用 regenerator 的工具,例如babel)的帮助下将代码转换为 ES5 来支持旧环境。


让函数接受回调

回调是指将函数 1 传递给函数 2 时。函数 2 可以在准备好时调用函数 1。在异步进程的上下文中,只要异步进程完成,就会调用回调。通常,结果传递给回调。

在问题的示例中,您可以foo接受回调并将其用作success回调。所以这

var result = foo();
// Code that depends on 'result'

变成

foo(function(result) {
    // Code that depends on 'result'
});

这里我们定义了“内联”函数,但您可以传递任何函数引用:

function myCallback(result) {
    // Code that depends on 'result'
}

foo(myCallback);

foo 本身定义如下:

function foo(callback) {
    $.ajax({
        // ...
        success: callback
    });
}

callback将引用foo我们调用时传递给的函数并将其传递给success. 即一旦 Ajax 请求成功,$.ajax将调用callback并将响应传递给回调(可以用 引用result,因为这是我们定义回调的方式)。

您还可以在将响应传递给回调之前对其进行处理:

function foo(callback) {
    $.ajax({
        // ...
        success: function(response) {
            // For example, filter the response
            callback(filtered_response);
        }
    });
}

使用回调编写代码比看起来更容易。毕竟,浏览器中的 JavaScript 是高度事件驱动的(dom 事件)。接收 Ajax 响应只不过是一个事件。当您必须使用第三方代码时,可能会出现困难,但大多数问题都可以通过考虑应用程序流程来解决。


ES2015+:使用then()

该承诺api是ECMAScript的6(ES2015)的新功能,但它有很好的浏览器支持了。还有许多库实现了标准的 Promises API,并提供了额外的方法来简化异步函数的使用和组合(例如,bluebird)。

承诺是未来价值的容器。当 promise 收到值(已解决)或被取消(拒绝)时,它会通知所有想要访问此值的“侦听器”。

与普通回调相比的优势在于它们允许您解耦您的代码并且它们更容易组合。

下面是一个使用 promise 的例子:

function delay() {
  // `delay` returns a promise
  return new Promise(function(resolve, reject) {
    // Only `delay` is able to resolve or reject the promise
    setTimeout(function() {
      resolve(42); // After 3 seconds, resolve the promise with value 42
    }, 3000);
  });
}

delay()
  .then(function(v) { // `delay` returns a promise
    console.log(v); // Log the value once it is resolved
  })
  .catch(function(v) {
    // Or do something else if it is rejected
    // (it would not happen in this example, since `reject` is not called).
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

应用于我们的 Ajax 调用,我们可以使用这样的承诺:

function ajax(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.onload = function() {
      resolve(this.responseText);
    };
    xhr.onerror = reject;
    xhr.open('GET', url);
    xhr.send();
  });
}

ajax("https://jsonplaceholder.typicode.com/todos/1")
  .then(function(result) {
    console.log(result); // Code depending on result
  })
  .catch(function() {
    // An error occurred
  });
.as-console-wrapper { max-height: 100% !important; top: 0; }

描述 promise 提供的所有优势超出了本答案的范围,但是如果您编写新代码,则应该认真考虑它们。它们为您的代码提供了很好的抽象和分离。

有关承诺的更多信息:html5 摇滚 - JavaScript 承诺。


旁注:jQuery 的延迟对象

延迟对象是 jQuery 的自定义承诺实现(在 Promise API 标准化之前)。它们的行为几乎与 Promise 类似,但公开的 API 略有不同。

jQuery 的每个 Ajax 方法都已经返回一个“延迟对象”(实际上是一个延迟对象的承诺),您可以从您的函数中返回它:

function ajax() {
    return $.ajax(...);
}

ajax().done(function(result) {
    // Code depending on result
}).fail(function() {
    // An error occurred
});


旁注:Promise陷阱

请记住,promise 和 deferred 对象只是未来值的容器,它们不是值本身。例如,假设您有以下内容:

function checkPassword() {
    return $.ajax({
        url: '/password',
        data: {
            username: $('#username').val(),
            password: $('#password').val()
        },
        type: 'POST',
        dataType: 'json'
    });
}

if (checkPassword()) {
    // Tell the user they're logged in
}

这段代码误解了上述异步问题。具体来说,$.ajax()在检查服务器上的“/password”页面时不会冻结代码 - 它向服务器发送请求,并在等待时立即返回一个 jQuery Ajax Deferred 对象,而不是来自服务器的响应。这意味着该if语句将始终获取此 Deferred 对象,将其视为true,并像用户已登录一样继续。不好。

但修复很简单:

checkPassword()
.done(function(r) {
    if (r) {
        // Tell the user they're logged in
    } else {
        // Tell the user their password was bad
    }
})
.fail(function(x) {
    // Tell the user something bad happened
});


不推荐:同步“Ajax”调用

正如我所提到的,一些(!)异步操作具有同步对应项。我不提倡使用它们,但为了完整起见,以下是执行同步调用的方式:

没有 jQuery

如果您直接使用XMLHttpRequest对象,请将其false作为第三个参数传递给.open.

jQuery

如果您使用jQuery,则可以将该async选项设置为false. 请注意,此选项自 jQuery 1.8 起已弃用。然后,您仍然可以使用success回调或访问jqXHR 对象的responseText属性:

function foo() {
    var jqXHR = $.ajax({
        //...
        async: false
    });
    return jqXHR.responseText;
}

如果使用任何其他的jQuery的Ajax的方法,例如$.get,$.getJSON等等,必须将其改为$.ajax(因为你只能传递配置参数$.ajax)。

当心!无法进行同步JSONP请求。JSONP 就其本质而言始终是异步的(甚至不考虑此选项的另一个原因)。


链接: https://www.fly63.com/article/detial/10694

挑战常规--不要使用||赋予默认值

js是一种弱类型的编程语言,代表着传入的变量并不清楚作为何种类型使用。对js来说传入的任意参数都应该考虑不同类型的结果,而不是单单考虑一种情况。若传入0、false等,||所要实现默认值的功能完全错误的

YodaOS 中是如何生成 API 的

本文简单介绍了 YodaOS 在 API 设计过程中,如何利用 DSL,解决 YodaOS API 在多种应用形态保持一致性。以此,我们希望抛砖引玉:帮助读者更好地了解 YodaOS API 的生成过程,帮助读者了解到 DSL,也能将这种思路应用在自己的项目中

JavaScript 私有成员

JavaScript 一直没有私有成员并不是没有原因,所以这一提议给 JavaScript 带来了新的挑战。但同时,JavaScript 在 ES2015 发布的时候已经在考虑私有化的问题了,所以要实现私有成员也并非毫无基础。

web前端学习之路

对于程序员来说,如果哪一天开始他停止了学习,那么他的职业生涯便开始宣告消亡。这不是什么危言耸听的怪语,而是一位大牛几年前告诉我的,他的信条。

web前端开发好学吗?

随着互联网+时代的到来,移动互联网行业的发展也是突飞猛进。无论你是否承认,这个时代已经被网页所包围了,这所有一切,都是前端工程师的杰作。今天给大家聊的就是\"前端真的好学吗?\"

Web前端小白入门

Web前端开发怎么入门,主要都有哪些要素组成?Web前端开发是由网页制作演变而来的,主要由HTML、CSS、JavaScript三大要素组成。专业的Web前端开发入门知识也一定会包含这些内容,下面就给大家简单介绍一下。

highcharts 时间少8小时问题

Highcharts 中默认开启了UTC(世界标准时间),由于中国所在时区为+8,所以经过 Highcharts 的处理后会减去8个小时。如果不想使用 UTC,有2种方法可供使用:

巧妙利用引用,将数组转换成树形数组

笔者所做的一个项目需要做一个前端的树形菜单,后端返回的数据是一个平行的list,list中的每个元素都是一个对象,例如list[0]的值为{id: 1, fid: 0, name: 一级菜单},每个元素都指定了父元素,生成的菜单可以无限级嵌套

NodeJS/JWT/Vue 实现基于角色的授权

在本教程中,我们将完成一个关于如何在 Node.js 中 使用 JavaScript ,并结合 JWT 认证,实现基于角色(role based)授权/访问的简单例子。作为例子的 API 只有三个路由,以演示认证和基于角色的授权

forward和redirect的区别?http状态码301,302分别代表什么?

从地址栏显示来说:forward是服务器内部重定向,客户端浏览器的网址不会发生变化;redirect发生一个状态码,告诉服务器去重新请求那个网址,显示的的新的网址数据共享:forward使用的是同一个request

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!