RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 554290
Accepted
Pavel Mayorov
Pavel Mayorov
Asked:2020-08-11 16:49:28 +0000 UTC2020-08-11 16:49:28 +0000 UTC 2020-08-11 16:49:28 +0000 UTC

如何从事件或回调函数中返回值?或者至少等他们完成。

  • 772

我正在尝试这样做,但它不起作用:

var result = "";

someInput.onchange = function() {
  result = someInput.value;
};

$.get("someapi", function (data) {
  result = data.foo;
});

some.api.call(42, function (data) {
  result = data.bar;
});

someDiv.textContent = result;

由于某种原因someDiv,它没有出现在任何东西中。

javascript ajax
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Pavel Mayorov
    2020-08-11T16:49:28Z2020-08-11T16:49:28Z

    问题是代码中没有等待操作。事件订阅、AJAX 调用,甚至 API 调用都不会等待数据到达——而是立即进一步转移控制。因此,该行在变量获取值之前someDiv.textContent = result;执行!result

    有几种方法可以在获取值后进行此赋值。

    方法 0 - 将分配移动到内部

    也许这种方法看起来有些愚蠢——但它解决了问题并且最容易理解。如果您的应用程序足够简单,那么您应该这样做。看:

    someInput.onchange = function() {
      someDiv.textContent = someInput.value;
    };
    
    $.get("someapi", function (data) {
      someDiv.textContent = data.foo;
    });
    
    some.api.call(42, function (data) {
      someDiv.textContent = data.bar;
    });
    
    someDiv.textContent = "";
    

    在这种情况下,我完全摆脱了变量result。

    这种方法的缺点恰恰是1——没有分层。数据在接收到的地方进行处理。如果您觉得使用此方法时您的脚本变得越来越难以理解,或者您必须在多个地方编写相同的内容,那么您需要转向其他方法。

    0+ 方法是将赋值移动到命名函数中。

    对先前方法的最简单修改,它可以让您摆脱代码重复。

    someInput.onchange = function() {
      setResult(someInput.value);
    };
    
    $.get("someapi", function (data) {
      setResult(data.foo);
    });
    
    some.api.call(42, function (data) {
      setResult(data.bar);
    });
    
    setResult("");
    
    function setResult(result) {
      someDiv.textContent = result;
    }
    

    让我提醒你,在 js 中,函数声明“转到顶部”,即 在最底部声明的函数setResult可以在任何地方使用。这允许您不使用 100500 函数的声明来启动脚本,而是使用将立即开始执行的代码。

    此方法适用于未分解为模块的小脚本。

    面食代码问题

    有时,在一个模块或其一部分中发出异步请求,其结果必须在另一个模块中接收。直接使用 0+ 方式会生成名为“pasta”的代码:

    // модуль 1
    function getResult() {
      $.get("someapi", function (data) {
        setResult(data.foo);
      });
    }
    
    // модуль 2
    function someFunc() {
      getResult();
    }
    function setResult(result) {
      someDiv.textContent = result;
    }
    

    我提请你注意:someFunc它调用getResult,它调用setResult。结果,两个模块互相调用。这是意大利面代码。

    为了处理此类代码,需要使用以下方法。

    方法 1 - 回调(“回调”,回调)

    让我们向发出请求的函数添加一个参数,callback我们将在其中传递接收响应的函数:

    function getResult(callback) {
      $.get("someapi", function (data) {
        callback(data.foo);
      });
    }
    

    现在可以这样调用这样的函数:

    getResult(function(result) {
      someDiv.textContent = result;
    })
    

    或者像这样:

    getResult(setResult);
    
    function setResult(result) {
      someDiv.textContent = result;
    }
    

    方法 2 - 承诺(“承诺”,承诺)

    js 中的 promise 是一种编程模式,表示一个当前不存在但预期在未来存在的值。

    有几种承诺的实现。现在最主要的是ES6 Promises,除了 IE 之外,现代浏览器都支持它们。(但对于那些不支持它们的浏览器,有一堆 polyfills)。

    Promise 是这样创建的:

    function getResult(N) {
      return new Promise(function (resolve, reject) {
        some.api.call(N, function (data) {
          resolve(data.bar);
        });
      });
    }
    

    您还可以使用jQuery Deferred作为承诺:

    function getResult(N) {
      var d = $.Deferred();
    
      some.api.call(N, function (data) {
        d.resolve(data.bar);
      });
    
      return d.promise();
    }
    

    或者Angular $q:

    function getResult(N) {
      var d = $q.defer();
    
      some.api.call(N, function (data) {
        d.resolve(data.bar);
      });
    
      return d.promise;
    }
    

    顺便说一下,Angular $q 也可以像 es6 promise 一样使用:

    function getResult(N) {
      return $q(function (resolve, reject) {
        some.api.call(N, function (data) {
          resolve(data.bar);
        });
      });
    }
    

    在任何情况下,像这样使用 getResult 函数看起来都是一样的:

    getResult(42).then(function (result) {
      someDiv.textContent = result;
    });
    

    或者您可以使用下面 Grundy 的回答中描述的新异步/等待语法

    我提请您注意,我在这里只是作为示例some.api.call,而不是事件或 ajax 调用 - 这并非偶然!

    关键是一个承诺只能被履行 ( resolved) 一次,而大多数事件会发生多次。因此,使用 promisesonchanged是不可能的。

    至于 ajax 调用,请记住它已经返回一个承诺!因此,上面的所有方法与它结合起来都会显得很可笑。一切都变得容易得多:

    function getResult() {
      return $.get("someapi")
        .then(function (data) {
          return data.foo;
        });
    }
    

    顺便说一下,你也可以在这里使用 async/await

    如果您对上面的代码感到困惑,这里是扩展版本:

    function getResult() {
      var q1 = $.get("someapi");
    
      var q2 = q1.then(function (data) {
        return data.foo;
      });
    
      return q2;
    }
    

    这里的一切都很简单。调用本身$.get返回一个承诺,执行时将包含来自服务器的数据。

    接下来,我们为它创建一个延续来处理这些数据(获取字段foo)。

    好吧,然后我们返回这个延续(这也是一个承诺)。

    方法 3 - 淘汰赛中的 Observables

    Knockout 通常被认为是一个用于双向数据绑定到视图的库 - 但它的功能在解决类似问题时可以派上用场。

    你可以这样做。首先,让我们得到一个可观察的值:

    var result = ko.observable("");
    

    这个值可以通过事件改变:

    someInput.onchange = function() {
      // вызов result с параметром устанавливает значение равным параметру
      result(someInput.value);
    };
    

    现在您可以在每次此值更改时执行一些代码块:

    ko.computed(function() {
      // вызов result без параметров возвращает текущее значение
      someDiv.textContent = result();
    });
    

    传递给的函数ko.computed将在每次其依赖项发生变化时被调用。

    PS 上面的代码是作为具有可观察值的手动工作的示例给出的。但请记住,Knockout 有更简单的方法来处理 DOM 元素的内容:

    var vm = {
      result: ko.observable()
    };
    ko.applyBindings(vm);
    
    <input data-bind="value: result"></input> <!-- бывший someInput -->
    
    <div data-bind="text: result"></div> <!-- бывший someDiv -->
    

    方法 3.1 - MobX 中的可观察对象

    这里的一切几乎和淘汰赛一样。在下面的示例中,我使用 ES2016 和更旧的语法,因为该库暗示使用新的语言功能:

    import { observable, autorun } from 'mobx';
    
    var result = observable("");
    
    someInput.onchange = () => {
      result.set(someInput.value);
    };
    
    autorun(() => someDiv.textContent = result.get());
    

    然而,MobX 通常使用类而不是单个可观察对象:

    class ViewModel {
      @observable result = "";
    }
    
    var vm = new ViewModel();
    
    someInput.onchange = () => {
      vm.result = someInput.value;
    };
    
    autorun(() => someDiv.textContent = vm.result);
    
    • 131
  2. Best Answer
    Grundy
    2020-08-11T18:19:37Z2020-08-11T18:19:37Z

    我们等了!ES2017 第 8 版。

    添加了带有 modifierasync和 use的函数的描述await

    该示例已在 chrome 中运行:

    (async function() {
      var data = await fetch('https://jsonplaceholder.typicode.com/users');
      console.log(await data.json());
    })();


    ES2015

    这个标准引入了生成器函数的概念——一个可以从中间转移控制然后返回到同一个地方的函数。通常它们用于获取序列

    function* foo(){
        yield 1;
        yield 2;
        while(true) yield 3;
    }
    

    此函数返回可迭代序列的迭代器1,2,3,3,3,...。虽然这本身很有趣,但有一个特殊情况。

    如果结果序列是一系列动作而不是数字,我们可以在每次运行动作时暂停函数并等待结果再返回函数。因此,我们得到的不是数字序列,而是未来值序列:即 承诺。

    这有点复杂,但是一个非常强大的技巧允许我们在同步模式下编写异步代码。有几个“启动器”可以做到这一点。例如,我将使用Promise.coroutinefrom Bluebird,但还有其他打包程序,例如соor Q.async。

    var foo = coroutine(function*(){
        var data = yield fetch("/echo/json"); // обратите внимание на yield
        // код здесь будет выполнен после получения ответа на запрос
        return data.json(); // data здесь определена
    });
    

    此方法还返回可在其他协程中使用的承诺。例如:

    var main = coroutine(function*(){
       var bar = yield foo(); // ожидаем окончания нашей сопрограммы она вернет обещание
       // код ниже выполнится когда будет получен ответ от сервера
       var baz = yield fetch("/api/users/"+bar.userid); // зависит от результата возвращенного функцией foo
       console.log(baz); // выполнится когда завершатся оба запроса
    });
    main();
    

    ES2016 (ES7) 不远的将来

    引入新关键字的标准中有一些提示可以更轻松async地await处理 promises。

    async function foo(){
        var data = await fetch("/echo/json"); // обратите внимание на await
        // код тут выполнится только после выполнения запроса
        return data.json(); // data определена
    }
    

    但目前,这些只是保留字,是否会进入下一个标准以及何时会有实施还不得而知。

    现在,您可以使用像Babel这样的汇编器来使用它们。

    这个答案的部分翻译

    • 95
  3. amaksr
    2020-06-21T21:41:00Z2020-06-21T21:41:00Z

    可以使用替代 JS 引擎nsynjs同步执行具有异步功能的代码

    如果异步函数返回一个承诺

    然后我们就调用这个函数,我们通过属性获取 promise 的值data:

    function synchronousCode() {
    
        var getURL = function(url) {
            return window.fetch(url).data.text().data;
        };
        
        var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
        console.log('received bytes:',getURL(url).length);
        
    };
    
    nsynjs.run(synchronousCode,{},function(){
        console.log('synchronousCode done');
    });
    <script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>

    如果异步函数调用回调

    第 1 步。我们将异步函数包装在 nsynjs 包装器中(或在 promise 中):

    var ajaxGet = function (ctx,url) {
        var res = {};
        var ex;
        $.ajax(url)
        .done(function (data) {
            res.data = data;
        })
        .fail(function(e) {
            ex = e;
        })
        .always(function() {
            ctx.resume(ex);
        });
        return res;
    };
    ajaxGet.nsynjsHasCallback = true;
    

    Шаг 2. Помещаем логику в функцию, как если бы логика исполнялась синхронно

    function process() {
        console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
    }
    

    Шаг 3. Исполняем функцию через nsynjs

    nsynjs.run(process,this,function () {
        console.log("synchronous function finished");
    });
    

    Nsynjs будет последовательно исполнять код функции, останавливаясь и дожидаясь результата вызовов всех аснихронных функций.

    • 23

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5