如何从异步调用返回响应?

2023-12-19

如何从函数返回响应/结果foo发出异步请求?

我试图从回调中返回值,并将结果分配给函数内的局部变量并返回该变量,但这些方法都没有实际返回响应 - 它们都返回undefined或者无论变量的初始值如何result is.

接受回调的异步函数示例(使用 jQuery 的ajax功能):

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`
}

→ 有关异步行为的更一般的解释以及不同的示例,请参阅 为什么我的变量在函数内部修改后没有改变? - 异步代码参考 https://stackoverflow.com/q/23667086/218196

→ 如果您已经了解该问题,请跳至下面的可能解决方案。

问题

The A in Ajax https://en.wikipedia.org/wiki/Ajax_(programming)代表异步 https://www.merriam-webster.com/dictionary/asynchronous。这意味着发送请求(或者更确切地说接收响应)被从正常执行流程中删除。在你的例子中,$.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(); has to wait直到函数返回结果。

异步

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

这正是您发出 Ajax 请求时所发生的情况。

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

不等待响应,而是立即继续执行并执行 Ajax 调用之后的语句。为了最终获得响应,您提供一个在收到响应后调用的函数,callback(注意到什么了吗?打回来?)。该调用之后的任何语句都会在调用回调之前执行。


解决方案

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

你问为什么不好?

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

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

下面我们将研究三种不同的解决方案,它们都是相互构建的:

  • 承诺与async/await(ES2017+,如果您使用转译器或再生器,则可在旧版浏览器中使用)
  • 回调(在节点中流行)
  • 承诺与then()(ES2015+,如果您使用众多 Promise 库之一,则可在旧版浏览器中使用)

所有这三个都可以在当前浏览器和 Node 7+ 中使用。


ES2017+:承诺async/await https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

2017年发布的ECMAScript版本介绍语法级支持对于异步函数。在...的帮助下async and await,您可以以“同步风格”编写异步。代码仍然是异步的,但更容易阅读/理解。

async/await建立在承诺之上:async函数总是返回一个承诺。await“解开”一个 Promise,要么产生 Promise 被解析的值,要么在 Promise 被拒绝时抛出错误。

重要的:你只能使用await里面一个async函数或在JavaScript 模块 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules。顶层await模块外部不支持,因此您可能必须创建一个异步 IIFE (立即调用函数表达式 https://en.wikipedia.org/wiki/Immediately_invoked_function_expression)开始一个async如果不使用模块,则使用上下文。

您可以阅读更多有关async https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function and await https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await on MDN.

这是一个详细说明的示例delay功能findItem() above:

// 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 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);
})();

Current browser https://kangax.github.io/compat-table/es2016plus/#test-async_functions and node http://node.green/#ES2017-features-async-functions版本支持async/await。您还可以通过将代码转换为 ES5 来支持旧环境再生器 https://github.com/facebook/regenerator(或使用再生器的工具,例如Babel https://babeljs.io/).


让函数接受回调

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

在问题的例子中,你可以foo接受回调并将其用作success打回来。所以这

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

becomes

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。 IE。一旦Ajax请求成功,$.ajax将会通知callback并将响应传递给回调(可以用result,因为这就是我们定义回调的方式)。

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

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

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


ES2015+:承诺then() https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

The 承诺API https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise是 ECMAScript 6 (ES2015) 的新特性,但它有很好的浏览器支持 http://caniuse.com/#feat=promises已经。还有许多库实现了标准 Promises API 并提供了额外的方法来简化异步函数的使用和组合(例如,bluebird https://github.com/petkaantonov/bluebird).

Promise 是容器future价值观。当 Promise 收到值时(它是resolved)或取消时(rejected),它通知所有想要访问该值的“监听者”。

与普通回调相比的优点是它们允许您解耦代码并且更容易编写。

这是使用 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 调用时,我们可以使用这样的 Promise:

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 的承诺 http://www.html5rocks.com/en/tutorials/es6/promises/.

旁注:jQuery 的延迟对象

延迟对象 https://stackoverflow.com/questions/4866721/what-are-deferred-objects是 jQuery 的 Promise 自定义实现(在 Promise API 标准化之前)。它们的行为几乎与 Promise 类似,但公开的 API 略有不同。

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

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

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

旁注:Promise 陷阱

请记住,承诺和延迟对象只是容器对于未来的价值,它们不是价值本身。例如,假设您有以下内容:

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 https://xhr.spec.whatwg.org/对象、传递false作为第三个参数.open https://xhr.spec.whatwg.org/#the-open()-method.

jQuery

如果你使用jQuery http://api.jquery.com/jQuery.ajax/,您可以设置async选项false。注意这个选项是已弃用从 jQuery 1.8 开始。 然后您仍然可以使用success回调或访问responseText的财产jqXHR 对象 http://api.jquery.com/jQuery.ajax/#jqXHR:

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

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

小心!无法实现同步JSONP https://stackoverflow.com/questions/2067472/please-explain-jsonp要求。 JSONP 本质上始终是异步的(这是不考虑此选项的又一个原因)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从异步调用返回响应? 的相关文章

  • 使用 Passport 进行 Node.js 身份验证:如果字段丢失,如何闪烁消息?

    我正在使用 Passport js 如果我的表单字段为空 我想显示一条消息 但我不知道该怎么做 因为如果缺少护照 则不会触发策略回调 我真的希望这个用例更加清晰 而且我不想修改护照 我感觉有办法 但不知道在哪里 我尝试使用路线的回调 app
  • 如何对页面的某个部分进行实时更新?

    我需要刷新页面的各个部分 以便在有新数据时进行更新 我该怎么办 使用jquery 例子 是的 jQuery 非常适合这个 查看这些方法 http api jquery com category ajax http api jquery co
  • 无法加载资源:服务器在已部署的 React.js 项目中响应状态为 404(未找到)

    当我将 React 项目部署到 Surge 中时 构建成功并且可以获取应用程序 URL 但是当我链接到 URL 时 我可以在检查控制台中看到一个空白页面和一些错误 Failed to load resource the server res
  • 是的验证;同一字段可以接受不同类型吗?

    我对是的很陌生 我试图验证字段可以是遵循某个正则表达式的字符串 也可以是此类字符串的数组 这是检查字符串与我的正则表达式匹配的工作示例 field yup string matches regex 现在我想要field如果它有一个这样的字符
  • 递归process.nextTick警告

    作为我的应用程序的一部分 我有以下代码行 process nextTick function pre populate cache with all users console log scanning users table in ord
  • 使用 javascript 和 jQuery UI datepicker 获取工作日数

    我有两个日期选择器 我可以从中计算天数 而无需计算星期六和星期日 但我想在周五和周六这样做 我尝试过一些不同的方法但失败了 对我来说 处理不包括周六和周日的天数很容易 但处理周五和周六的天数则不然 以下是我的 javascript 代码 f
  • Array.from 的时间复杂度

    时间复杂度是多少Array from 例如 const set new Set set add car set add cat set add dog console log Array from set time complexity o
  • 将反序列化方法转换为异步

    我正在尝试使用 Async Await 转换此将对象反序列化为字符串的方法 public static T DeserializeObject
  • 如何在Javascript中保存zip文件的二进制数据?

    我从 AJAX 响应中收到以下响应 这是 zip 文件的响应 请让我知道如何在 Javascript 中保存此 filename zip ZIP 里面有 PDF 文件 我的代码是这样的 ajax url baseURLDownload se
  • 如何拆分字符串,在特定字符处断开?

    我有这个字符串 john smith 123 Street Apt 4 New York NY 12345 使用 JavaScript 将其解析为最快的方法是什么 var name john smith var street 123 Str
  • Django 管理中的自定义依赖下拉菜单

    我有一个按阶段模型的项目外键 我很难在 Django 管理页面中创建依赖的下拉列表 我想当用户从该项目的 项目下拉 阶段选择一个项目时 在第二个下拉菜单中显示 实现这一目标的最佳方法是什么 如果下拉列表根据其父级的值来过滤项目 那就太好了
  • 将变量从一个 jsp 发送到另一个 jsp

    我有一个 JSP 文件jsp 1 jsp和另一个 JSP 文件jsp 2 jsp 我已经包括了jsp 2 jsp in jsp 1 jsp using 现在我需要某个元素上的单击事件 在该事件中 我想将字符串变量传输到包含的 jsp 中 假
  • 带搜索框的 D3 图表

    我在 D3 中创建了一个图表 其中节点显示特定个人创建文档的时间 该图表还显示了一个搜索框 该搜索框根据搜索框输入是否与与该文档关联的单词匹配而将节点变成红色 这些单词列在数据集的第 5 列中 请参阅下面的数据集 我的问题 一旦将搜索输入到
  • jQuery 分钟和秒倒计时器

    我想创建一个 jquery 倒计时器 我尝试了以下代码 但它不起作用 我该怎么办 DEMO https jsfiddle net tbosn210 https jsfiddle net tbosn210 var interval setIn
  • 如何为 IAsyncOperation 指定回调方法

    是否可以指定一个方法完成后调用async手术 平台 C Windows Phone 8 我需要实现非阻塞方法来异步发送UDP数据包 他们有我的方法 onWriteComplete int errorCode 操作完成时回调 这是我尝试过的
  • 如何使用 $.ajax 发送 JSON 而不是查询字符串?

    有人可以简单地解释一下如何让 jQuery 发送实际的 JSON 而不是查询字符串吗 ajax url url dataType json I was pretty sure this would do the trick data dat
  • ag-Grid 中的行格式

    我们需要有条件地将行文本设置为粗体 目前它仅适用于单个单元格 但我们需要在所有列单元格上应用文本粗体 应用格式设置后 isBold 列必须隐藏 删除 此列仅用于格式化 如何应用文本缩进 10px isBold 列包含真实值的第一列的 有可能
  • CORS:为什么我的浏览器不发送 OPTIONS 预检请求?

    从我读到的内容来看CORS https en wikipedia org wiki Cross origin resource sharing 我理解它应该按如下方式工作 客户端的脚本尝试获取资源从服务器不同的起源 浏览器拦截这个请求并首先
  • getCompatedStyle 类似于 IE8 的 javascript 函数

    我正在尝试在 Java GWT 代码中编写一个 Javascript 函数 该函数获取以下样式的值 direction fontFamily fontSize fontSizeAdjust fontStyle fontWeight lett
  • 如何捕获 google 地图的无效 API 密钥

    我有这个代码 如果密钥无效 则会弹出警报 但我想在这种情况下执行一些操作 但我不知道如何连接它 有任何想法吗 Google 不提供检查 Google 地图 API 密钥的外部方法 因此 您无法使用例如查询某些服务 此代码有效吗abcde12

随机推荐