如何从异步调用返回响应

IT技术 javascript ajax asynchronous
2020-12-26 21:00:54

我有一个foo发出异步请求的函数如何从 返回响应/结果foo

我试图从回调中返回值,并将结果分配给函数内部的一个局部变量并返回那个,但是这些方法都没有真正返回响应(它们都返回undefined或变量的初始值result是)。

接受回调的异步函数示例(使用 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`
}

使用thenPromise块的示例

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`
}
6个回答

→ 有关不同示例的异步行为的更一般性解释,请参阅 为什么我的变量在函数内部修改后未更改?- 异步代码参考

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

问题

的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+:Promise async/await

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

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

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

你可以阅读更多关于asyncawait的MDN。

这是一个详细说明上述延迟功能的示例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() 进行Promise

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

Promise是未来value的容器当 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 调用,我们可以使用这样的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 提供的所有优势超出了本答案的范围,但是如果您编写新代码,则应该认真考虑它们。它们为您的代码提供了很好的抽象和分离。

有关Promise的更多信息:HTML5 摇滚 - JavaScript Promise

旁注:jQuery 的延迟对象

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

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

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 就其本质而言始终是异步的(甚至不考虑此选项的另一个原因)。

@Jessi:我认为您误解了答案的那一部分。$.getJSON如果您希望 Ajax 请求同步,则不能使用但是,您不应该希望请求是同步的,因此这不适用。您应该使用回调或Promise来处理响应,正如在前面的答案中所解释的那样。
2021-02-07 21:00:54
在解决方案 1,子 jQuery 中,我无法理解这一行:(If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.是的,我意识到在这种情况下我的昵称有点讽刺)
2021-02-11 21:00:54
@Pommy:如果你想使用 jQuery,你必须包含它。请参阅docs.jquery.com/Tutorials:Getting_Started_with_jQuery
2021-02-28 21:00:54
这个问题的聊天已经死了,所以我不知道在哪里提出概述的更改,但我建议:1)将同步部分更改为简单讨论为什么它很糟糕,没有代码示例如何做到这一点。2)删除/合并回调示例以仅显示更灵活的延迟方法,我认为对于那些学习 Javascript 的人来说,这也可能更容易理解。
2021-03-02 21:00:54
@gibberish:嗯,我不知道如何更清楚。你看到如何foo调用和传递一个函数给它 ( foo(function(result) {....});) 吗?result在这个函数内部使用,是 Ajax 请求的响应。要引用此函数,将调用 foo 的第一个参数callback并将其分配给success而不是匿名函数。所以,$.ajaxcallback在请求成功时调用我试着再解释一下。
2021-03-06 21:00:54

如果您没有在代码中使用 jQuery,则此答案适合您

你的代码应该是这样的:

function foo() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
    return httpRequest.responseText;
}

var result = foo(); // Always ends up being 'undefined'

Felix Kling 在为使用 jQuery for AJAX 的人编写答案方面做得很好,但我决定为没有使用 jQuery 的人提供替代方案。

请注意,对于那些使用新fetchAPI、Angular 或 promises 的人,我在下面添加了另一个答案


你所面临的

这是另一个答案中“问题的解释”的简短摘要,如果您在阅读本文后不确定,请阅读。

AJAX 中A代表异步这意味着发送请求(或者更确切地说是接收响应)已经脱离了正常的执行流程。在您的示例中,.send立即返回,并且在return result;您作为success回调传递的函数甚至被调用之前执行下一条语句

这意味着当您返回时,您定义的侦听器尚未执行,这意味着您返回的值尚未定义。

这是一个简单的类比:

function getFive(){
    var a;
    setTimeout(function(){
         a=5;
    },10);
    return a;
}

(小提琴)

a返回的值undefined因为该a=5部分尚未执行。AJAX 的行为是这样的,您在服务器有机会告诉浏览器该值是什么之前返回该值。

一个可能的解决这个问题是代码重新活跃,告诉你的程序在计算完成后做什么。

function onComplete(a){ // When the code completes, do this
    alert(a);
}

function getFive(whenDone){
    var a;
    setTimeout(function(){
         a=5;
         whenDone(a);
    },10);
}

这称为CPS基本上,我们正在传递getFive一个要在它完成时执行的操作,我们告诉我们的代码在事件完成时如何做出react(例如我们的 AJAX 调用,或者在这种情况下是超时)。

用法是:

getFive(onComplete);

这应该在屏幕上提醒“5”。(小提琴)

可能的解决方案

基本上有两种方法可以解决这个问题:

  1. 使 AJAX 调用同步(我们称之为 SJAX)。
  2. 重构您的代码以使用回调正常工作。

1. 同步 AJAX - 不要这样做!!

至于同步AJAX,就别做了!Felix 的回答提出了一些令人信服的论点,说明为什么这是一个坏主意。总而言之,它会冻结用户的浏览器,直到服务器返回响应并造成非常糟糕的用户体验。这是从 MDN 中摘取的另一个简短摘要,说明原因:

XMLHttpRequest 支持同步和异步通信。然而,一般而言,出于性能原因,异步请求应优先于同步请求。

简而言之,同步请求会阻塞代码的执行…………这会导致严重的问题……

如果你必须这样做,你可以传递一个标志。这是如何

var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false);  // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {// That's HTTP for 'ok'
  console.log(request.responseText);
}

2. 重构代码

让您的函数接受回调。在示例代码中foo可以接受回调。我们将告诉我们的代码完成如何做出reactfoo

所以:

var result = foo();
// Code that depends on `result` goes here

变成:

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

这里我们传递了一个匿名函数,但我们也可以很容易地传递对现有函数的引用,使其看起来像:

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

有关如何完成此类回调设计的更多详细信息,请查看 Felix 的回答。

现在,让我们定义 foo 本身以采取相应的行动

function foo(callback) {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onload = function(){ // When the request is loaded
       callback(httpRequest.responseText);// We're calling our method
    };
    httpRequest.open('GET', "/echo/json");
    httpRequest.send();
}

(小提琴)

我们现在让我们的foo函数接受一个动作,当 AJAX 成功完成时运行。我们可以通过检查响应状态是否不是 200 并采取相应措施(创建失败处理程序等)来进一步扩展这一点。它有效地解决了我们的问题。

如果您仍然难以理解这一点,请阅读MDN 上的 AJAX 入门指南

“同步请求会阻塞代码的执行并可能泄漏内存和事件” 同步请求如何泄漏内存?
2021-02-18 21:00:54

XMLHttpRequest 2(首先阅读Benjamin GruenbaumFelix Kling的回答

如果您不使用 jQuery 并且想要一个适用于现代浏览器和移动浏览器的简短 XMLHttpRequest 2,我建议您这样使用它:

function ajax(a, b, c){ // URL, callback, just a placeholder
  c = new XMLHttpRequest;
  c.open('GET', a);
  c.onload = b;
  c.send()
}

如你看到的:

  1. 它比列出的所有其他功能都短。
  2. 回调是直接设置的(所以没有额外的不必要的关闭)。
  3. 它使用新的 onload(因此您不必检查 readystate && status)
  4. 还有一些我不记得的其他情况使 XMLHttpRequest 1 很烦人。

有两种方法可以获取此 Ajax 调用的响应(三种使用 XMLHttpRequest 变量名称):

最简单的:

this.response

或者如果由于某种原因你bind()回调到一个类:

e.target.response

例子:

function callback(e){
  console.log(this.response);
}
ajax('URL', callback);

或者(上面那个更好的匿名函数总是有问题):

ajax('URL', function(e){console.log(this.response)});

没有什么比这更容易的了。

现在有些人可能会说最好使用 onreadystatechange 甚至 XMLHttpRequest 变量名。那是错误的。

查看XMLHttpRequest 高级功能

它支持所有*现代浏览器。我可以确认,自从创建 XMLHttpRequest 2 以来我一直在使用这种方法。在我使用的任何浏览器中,我从未遇到过任何类型的问题。

onreadystatechange 仅在您想获取状态 2 的标头时才有用。

使用XMLHttpRequest变量名是另一个大错误,因为您需要在 onload/oreadystatechange 闭包内执行回调,否则您将丢失它。


现在,如果您想要使用POST和 FormData 进行更复杂的操作,您可以轻松扩展此功能:

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.send(d||null)
}

再说一遍……这是一个非常短的函数,但它可以执行GET和 POST。

用法示例:

x(url, callback); // By default it's GET so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set POST data

或者传递一个完整的表单元素 ( document.getElementsByTagName('form')[0]):

var fd = new FormData(form);
x(url, callback, 'post', fd);

或者设置一些自定义值:

var fd = new FormData();
fd.append('key', 'val')
x(url, callback, 'post', fd);

如您所见,我没有实现同步……这是一件坏事。

话虽如此……为什么我们不以简单的方式来做呢?


正如评论中提到的,使用 error && synchronous 确实完全打破了答案的重点。以正确的方式使用 Ajax 的捷径是什么?

错误处理程序

function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder
  c = new XMLHttpRequest;
  c.open(e||'get', a);
  c.onload = b;
  c.onerror = error;
  c.send(d||null)
}

function error(e){
  console.log('--Error--', this.type);
  console.log('this: ', this);
  console.log('Event: ', e)
}
function displayAjax(e){
  console.log(e, this);
}
x('WRONGURL', displayAjax);

在上面的脚本中,您有一个静态定义的错误处理程序,因此它不会影响功能。错误处理程序也可用于其他功能。

但是要真正解决错误,唯一的方法是编写错误的 URL,在这种情况下,每个浏览器都会抛出错误。

如果您设置自定义标头、将 responseType 设置为 blob 数组缓冲区或其他任何内容,则错误处理程序可能很有用...

即使您将 'POSTAPAPAP' 作为方法传递,它也不会抛出错误。

即使您将 'fdggdgilfdghfldj' 作为 formdata 传递,它也不会抛出错误。

在第一种情况下,错误位于asdisplayAjax()之下this.statusTextMethod not Allowed

在第二种情况下,它很简单。如果您传递了正确的帖子数据,您必须在服务器端检查。

不允许跨域自动抛出错误。

在错误响应中,没有任何错误代码。

只有this.type设置为error 的

如果您完全无法控制错误,为什么还要添加错误处理程序?大多数错误都在回调函数中的 this 中返回displayAjax()

所以:如果您能够正确复制和粘贴 URL,则不需要进行错误检查。;)

PS:作为第一个测试,我写了 x('x', displayAjax)...,它完全得到了响应...???所以我检查了HTML所在的文件夹,有一个名为'x.xml'的文件。因此,即使您忘记了文件的扩展名 XMLHttpRequest 2 也会找到它我笑了


同步读取文件

不要那样做。

如果您想暂时阻止浏览器,请.txt同步加载一个不错的大文件。

function omg(a, c){ // URL
  c = new XMLHttpRequest;
  c.open('GET', a, true);
  c.send();
  return c; // Or c.response
}

现在你可以做

 var res = omg('thisIsGonnaBlockThePage.txt');

没有其他方法可以以非异步方式执行此操作。(是的,使用 setTimeout 循环……但说真的?)

另一点是……如果您使用 API 或仅使用您自己的列表文件或任何您总是为每个请求使用不同函数的东西……

仅当您有一个页面始终加载相同的 XML/JSON 或任何您只需要一个功能的页面时。在这种情况下,稍微修改 Ajax 函数并将 b 替换为您的特殊函数。


以上功能为基本使用。

如果要扩展功能...

是的你可以。

我使用了很多 API,我集成到每个 HTML 页面的第一个函数是这个答案中的第一个 Ajax 函数,只有 GET ......

但是您可以使用 XMLHttpRequest 2 做很多事情:

我制作了一个下载管理器(在简历、文件阅读器和文件系统的两端使用范围),使用画布的各种图像调整器转换器,使用 base64images 填充 Web SQL 数据库等等......

但是在这些情况下,您应该仅为该目的创建一个函数……有时您需要一个 blob、数组缓冲区,您可以设置标题、覆盖 mimetype 以及更多……

但这里的问题是如何返回 Ajax 响应......(我添加了一个简单的方法。)

虽然这个答案很好(而且我们都喜欢XHR2 并且发布文件数据和多部分数据非常棒) - 这显示了使用 JavaScript 发布 XHR 的语法糖 - 你可能想把它放在博客文章中(我喜欢它)甚至在图书馆(不确定名称xajax或者xhr可能更好:))。我没有看到它如何解决从 AJAX 调用返回响应的问题。(有人仍然可以这样做但var res = x("url")不明白为什么它不起作用;))。附带说明 - 如果您c从该方法返回,那么用户可以挂钩error会很酷
2021-02-20 21:00:54
2.ajax is meant to be async.. so NO var res=x('url').. 这就是这个问题和答案的全部内容:)
2021-02-22 21:00:54
@cocco 所以你在 SO答案写了误导性的、不可读的代码以节省几次按键?请不要那样做。
2021-03-01 21:00:54

如果您使用 Promise,则此答案适合您。

这意味着 AngularJS、jQuery(带有延迟)、原生XHR的替换(fetch)、Ember.jsBackbone.js的保存或任何返回 promise 的Node.js库。

你的代码应该是这样的:

function foo() {
    var data;
    // Or $.get(...).then, or request(...).then, or query(...).then
    fetch("/echo/json").then(function(response){
        data = response.json();
    });
    return data;
}

var result = foo(); // 'result' is always undefined no matter what.

Felix Kling为使用带有 Ajax 回调的 jQuery 的人编写了一个很好的答案。我有一个关于原生 XHR 的答案。此答案适用于前端或后端的 Promise 的一般用法。


核心问题

浏览器中和带有 Node.js/io.js 的服务器上的 JavaScript 并发模型是异步react式的

每当你调用一个返回Promise的方法时,then处理程序总是异步执行——也就是说,它们下面的代码之后,不在.then处理程序中。

这意味着当您返回您定义datathen处理程序时,处理程序尚未执行。这反过来意味着您返回的值没有及时设置为正确的值。

这是这个问题的一个简单类比:

    function getFive(){
        var data;
        setTimeout(function(){ // Set a timer for one second in the future
           data = 5; // After a second, do this
        }, 1000);
        return data;
    }
    document.body.innerHTML = getFive(); // `undefined` here and not 5

的值dataundefined因为该data = 5部分尚未执行。它可能会在一秒钟内执行,但到那时它与返回值无关。

由于操作尚未发生(Ajax、服务器调用、I/O 和计时器),您将在请求有机会告诉您的代码该值是什么之前返回该值。

一个可能的解决这个问题是代码重新活跃,告诉你的程序在计算完成后做什么。Promise 本质上是时间性的(时间敏感的),从而积极地实现了这一点。

快速回顾一下Promise

Promise 是一个随时间变化Promise 有状态。它们开始时没有任何value,可以设置为:

  • 完成意味着计算成功完成。
  • 拒绝意味着计算失败。

一个Promise只能改变一次状态之后它将永远保持在同一状态。您可以将then处理程序附加到 Promise 以提取它们的值并处理错误。then处理程序允许链接调用。Promise 是通过使用返回它们的 API创建的例如,更现代的 Ajax 替换fetch或 jQuery 的$.get返回Promise。

当我们调用.then一个 promise 并从中返回一些东西时——我们得到了一个对处理过的 value的 promise 如果我们返回另一个Promise,我们会得到惊人的东西,但让我们抓住我们的马。

带着Promise

让我们看看如何用 Promise 解决上述问题。首先,让我们通过使用Promise 构造函数创建延迟函数来证明我们对上面的Promise状态的理解

function delay(ms){ // Takes amount of milliseconds
    // Returns a new promise
    return new Promise(function(resolve, reject){
        setTimeout(function(){ // When the time is up,
            resolve(); // change the promise to the fulfilled state
        }, ms);
    });
}

现在,在我们将 setTimeout 转换为使用 promises 之后,我们可以使用then它来计数:

function delay(ms){ // Takes amount of milliseconds
  // Returns a new promise
  return new Promise(function(resolve, reject){
    setTimeout(function(){ // When the time is up,
      resolve(); // change the promise to the fulfilled state
    }, ms);
  });
}

function getFive(){
  // We're RETURNING the promise. Remember, a promise is a wrapper over our value
  return delay(100).then(function(){ // When the promise is ready,
      return 5; // return the value 5. Promises are all about return values
  })
}
// We _have_ to wrap it like this in the call site, and we can't access the plain value
getFive().then(function(five){
   document.body.innerHTML = five;
});

基本上,而不是返回一个值,我们不能因为并发模型做-我们返回一个包装的value,我们可以解开then它就像一个可以打开的盒子then

应用这个

这与您的原始 API 调用相同,您可以:

function foo() {
    // RETURN the promise
    return fetch("/echo/json").then(function(response){
        return response.json(); // Process it inside the `then`
    });
}

foo().then(function(response){
    // Access the value inside the `then`
})

所以这也同样有效。我们已经了解到我们不能从已经异步的调用中返回值,但是我们可以使用 Promise 并将它们链接起来来执行处理。我们现在知道如何从异步调用返回响应。

ES2015 (ES6)

ES6 引入了生成器,它们是可以在中间返回然后恢复到它们所在点的函数。这通常对序列有用,例如:

function* foo(){ // Notice the star. This is ES6, so new browsers, Nodes.js, and io.js only
    yield 1;
    yield 2;
    while(true) yield 3;
}

是一个函数,它在可以迭代的序列上返回一个迭代器1,2,3,3,3,3,....虽然这本身很有趣,并为很多可能性开辟了空间,但有一个特别有趣的案例。

如果我们生成的序列是一系列动作而不是数字——我们可以在动作产生时暂停函数,并在恢复函数之前等待它。因此,我们需要一个未来序列,而不是数字序列——即:promise。

这是一个有点棘手但非常强大的技巧,让我们以同步方式编写异步代码。有几个“跑步者”可以为您执行此操作。编写一个是短短的几行代码,但这超出了本答案的范围。我将在Promise.coroutine这里使用 Bluebird ,但还有其他包装器,例如coQ.async

var foo = coroutine(function*(){
    var data = yield fetch("/echo/json"); // Notice the yield
    // The code here only executes _after_ the request is done
    return data.json(); // 'data' is defined
});

这个方法本身返回一个promise,我们可以从其他协程中使用它。例如:

var main = coroutine(function*(){
   var bar = yield foo(); // Wait our earlier coroutine. It returns a promise
   // The server call is done here, and the code below executes when done
   var baz = yield fetch("/api/users/" + bar.userid); // Depends on foo's result
   console.log(baz); // Runs after both requests are done
});
main();

ES2016 (ES7)

在 ES7 中,这被进一步标准化。现在有几个提案,但在所有提案中,您都可以await做出Promise。通过添加asyncawait关键字,这只是上面 ES6 提案的“糖”(更好的语法)制作上面的例子:

async function foo(){
    var data = await fetch("/echo/json"); // Notice the await
    // code here only executes _after_ the request is done
    return data.json(); // 'data' is defined
}

它仍然返回一个相同的Promise:)

您错误地使用了 Ajax。这个想法不是让它返回任何东西,而是将数据交给一个叫做回调函数的东西,它处理数据。

那是:

function handleData( responseData ) {

    // Do what you want with the data
    console.log(responseData);
}

$.ajax({
    url: "hi.php",
    ...
    success: function ( data, status, XHR ) {
        handleData(data);
    }
});

在提交处理程序中返回任何东西都不会做任何事情。相反,您必须传递数据,或者直接在成功函数中使用它做您想做的事情。

这个答案是完全语义化的……您的成功方法只是回调中的回调。你可以拥有success: handleData它,它会起作用。
2021-02-11 21:00:54