我如何从函数 foo 返回一个无同步请求的答案/结果?

我正在尝试从呼叫返回的值,以及将结果分配到函数内部的本地变量,并返回其中一个,但没有这些方式实际上返回答案 - 他们都返回不确定的或无论变量结果的初始值是什么。

一个不同步函数的例子,接受召回(使用 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`
}

例如,使用那时承诺的区块:

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

当前回答

简单的代码例子将 XHR 在 Node.js 转换为 async-await

var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhttp = new XMLHttpRequest();

function xhrWrapWithPromise() {
  return new Promise((resolve, reject) => {
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4) {
        if (this.status == 200) {
          resolve(this.responseText);
        } else {
          reject(new Error("Couldn't feth data finally"));
        }
      }
    };
    xhttp.open("GET", "https://www.w3schools.com/xml/xmlhttp_info.txt", true);
    xhttp.send();
  });
}

// We need to wrap await in Async function so and anonymous IIFE here
(async _ => {
  try {
    let result = await xhrWrapWithPromise();
    console.log(result);
  } catch (error) {
    console.log(error);
  }
})();

其他回答

在阅读这里的所有回复和我的经验后,我希望重复在JavaScript中无同步编程的呼叫、承诺和Async/等待的细节。

const body = document.getElementsByTagName('body')[0];
function callback() {
  console.log('Hello');
}
body.addEventListener('click', callback);

asyncCallOne(function callback1() {
  asyncCallTwo(function callback2() {
    asyncCallThree(function callback3() {
        ...
    })
  })
})

const myFirstPromise = new Promise((resolve, reject) => {
  // We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
  // In this example, we use setTimeout(...) to simulate async code.
  // In reality, you will probably be using something like XHR request or an HTML5 API.
  setTimeout(() => {
    resolve("Success!")  // Yay! Everything went well!
  }, 250)
})

myFirstPromise
  .then((res) => {
    return res.json();
  })
  .then((data) => {
    console.log(data);
  })
  .catch((e) => {
    console.log(e);
  });

then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.

Async 函数为我们提供一个干净和细致的合成,使我们能够写得更少的代码,以实现相同的结果,我们会得到的承诺. Async/Await 看起来类似于同步代码,同步代码更容易阅读和写作。

const getExchangeRate = async () => {
  try {
    const res = await fetch('https://getExchangeRateData');
    const data = await res.json();
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

getExchangeRate();

结论:这些完全是JavaScript中的无同步编程的三个合成,你应该明白,所以如果可能的话,我建议你使用“承诺”或“async/await”来恢复你的无同步代码(主要是XHR请求)!

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

var item = findItem();

// Do something with item
doSomethingElse();

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



重要: 您只能在一个 async 函数或 JavaScript 模块中使用等待。 顶级等待不支持模块之外,因此您可能需要创建一个 async IIFE (即时邀请函数表达) 以启动一个 async 背景,如果不使用模块。

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


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);

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

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


更多关于承诺的信息: HTML5 Rocks - JavaScript 承诺。

副注:jQuery的废弃物品

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

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

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
}

但修正很容易:

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

jQuery

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

如果您使用任何其他 jQuery Ajax 方法,如 $.get, $.getJSON 等,您必须将其更改为 $.ajax (因为您只能将配置参数转移到 $.ajax)。

使用承诺

这个问题的最完美答案是使用承诺。

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

使用

ajax("GET", "/test", "acrive=1").then(function(result) {
    // Code depending on result
})
.catch(function() {
    // An error occurred
});

但是等......!

使用承诺有问题!

為什麼我們應該使用我們自己的自定義承諾?

我一直在使用这个解决方案一段时间,直到我发现有一个错误在旧的浏览器:

未完成参考错误:承诺未定义

因此,我决定在未定义的情况下,将我的自己的承诺类为ES3实施到下面的JavaScript编辑器。

if(typeof Promise === "undefined"){
    function _typeof(obj) { "@babel/helpers - typeof"; return 

    _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
    function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
    function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
    function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
    function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
    function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
    function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
    function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
    function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
    function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
    function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
    var Promise = /*#__PURE__*/function () {
  "use strict";

  function Promise(main) {
    _classCallCheck(this, Promise);
    this.main = main;
    this.mainExecuted = false;
    this.resolved = false;
    this.rejected = false;
    this.promiseChain = [];
    this.handleError = function () {};
    this.onResolve = this.onResolve.bind(this);
    this.onReject = this.onReject.bind(this);
  }
  _createClass(Promise, [{
    key: "then",
    value: function then(handleSuccess) {
      if (this.resolved) {
        if (!this.rejected) {
          this.args = handleSuccess(this.args);
        }
      } else {
        this.promiseChain.push(handleSuccess);
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "catch",
    value: function _catch(handleError) {
      this.handleError = handleError;
      if (!this.mainExecuted) {
        this.main(this.onResolve, this.onReject);
        this.thenExecuted = true;
      }
      return this;
    }
  }, {
    key: "onResolve",
    value: function onResolve() {
      var _this = this;
      this.resolved = true;
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      this.args = args;
      try {
        this.promiseChain.forEach(function (nextFunction) {
          _this.args = nextFunction.apply(void 0, _toConsumableArray(_this.args));
        });
      } catch (error) {
        this.promiseChain = [];
        this.onReject(error);
      }
    }
  }, {
    key: "onReject",
    value: function onReject(error) {
      this.rejected = true;
      this.handleError(error);
    }
  }]);
  return Promise;
}();
}

如果您不使用 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'

(注意,对于使用新的Fetch API、Angular 或承诺的人,我在下面添加了另一个答案)


你所面临的

这里有一个简单的模拟:

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

(重定向)

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

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

使用将是:

getFive(onComplete);

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

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

二、修复代码

如:

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);

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开始指南。

这是其中一个地方,在许多新的JavaScript框架中使用的双向数据连接或存储概念将为您工作。

因此,如果您使用 Angular, React 或任何其他框架,使双向数据连接或存储概念,这个问题只是为您固定,所以简单地说,您的结果在第一阶段是不确定的,所以您在收到数据之前得到了结果 = 不确定的,然后一旦您获得结果,它将被更新并被分配到新的值,你的 Ajax 呼叫的反应。

但是,你如何在纯粹的JavaScript或jQuery中这样做,例如,你在这个问题上问的那样?

例如,在承诺中,我们有某些功能,如成功() 或之后() 将执行,当您的数据为您准备好。

$(document).ready(function(){
    function foo() {
        $.ajax({url: "api/data", success: function(data){
            fooDone(data); // After we have data, we pass it to fooDone
        }});
    };

    function fooDone(data) {
        console.log(data); // fooDone has the data and console.log it
    };

    foo(); // The call happens here
});