我正在尝试这样做,但它不起作用:
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
,它没有出现在任何东西中。
问题是代码中没有等待操作。事件订阅、AJAX 调用,甚至 API 调用都不会等待数据到达——而是立即进一步转移控制。因此,该行在变量获取值之前
someDiv.textContent = result;
执行!result
有几种方法可以在获取值后进行此赋值。
方法 0 - 将分配移动到内部
也许这种方法看起来有些愚蠢——但它解决了问题并且最容易理解。如果您的应用程序足够简单,那么您应该这样做。看:
在这种情况下,我完全摆脱了变量
result
。这种方法的缺点恰恰是1——没有分层。数据在接收到的地方进行处理。如果您觉得使用此方法时您的脚本变得越来越难以理解,或者您必须在多个地方编写相同的内容,那么您需要转向其他方法。
0+ 方法是将赋值移动到命名函数中。
对先前方法的最简单修改,它可以让您摆脱代码重复。
让我提醒你,在 js 中,函数声明“转到顶部”,即 在最底部声明的函数
setResult
可以在任何地方使用。这允许您不使用 100500 函数的声明来启动脚本,而是使用将立即开始执行的代码。此方法适用于未分解为模块的小脚本。
面食代码问题
有时,在一个模块或其一部分中发出异步请求,其结果必须在另一个模块中接收。直接使用 0+ 方式会生成名为“pasta”的代码:
我提请你注意:
someFunc
它调用getResult
,它调用setResult
。结果,两个模块互相调用。这是意大利面代码。为了处理此类代码,需要使用以下方法。
方法 1 - 回调(“回调”,回调)
让我们向发出请求的函数添加一个参数,
callback
我们将在其中传递接收响应的函数:现在可以这样调用这样的函数:
或者像这样:
方法 2 - 承诺(“承诺”,承诺)
js 中的 promise 是一种编程模式,表示一个当前不存在但预期在未来存在的值。
有几种承诺的实现。现在最主要的是ES6 Promises,除了 IE 之外,现代浏览器都支持它们。(但对于那些不支持它们的浏览器,有一堆 polyfills)。
Promise 是这样创建的:
您还可以使用jQuery Deferred作为承诺:
或者Angular $q:
顺便说一下,Angular $q 也可以像 es6 promise 一样使用:
在任何情况下,像这样使用 getResult 函数看起来都是一样的:
或者您可以使用下面 Grundy 的回答中描述的新异步/等待语法
我提请您注意,我在这里只是作为示例
some.api.call
,而不是事件或 ajax 调用 - 这并非偶然!关键是一个承诺只能被履行 (
resolved
) 一次,而大多数事件会发生多次。因此,使用 promisesonchanged
是不可能的。至于 ajax 调用,请记住它已经返回一个承诺!因此,上面的所有方法与它结合起来都会显得很可笑。一切都变得容易得多:
顺便说一下,你也可以在这里使用 async/await
如果您对上面的代码感到困惑,这里是扩展版本:
这里的一切都很简单。调用本身
$.get
返回一个承诺,执行时将包含来自服务器的数据。接下来,我们为它创建一个延续来处理这些数据(获取字段
foo
)。好吧,然后我们返回这个延续(这也是一个承诺)。
方法 3 - 淘汰赛中的 Observables
Knockout 通常被认为是一个用于双向数据绑定到视图的库 - 但它的功能在解决类似问题时可以派上用场。
你可以这样做。首先,让我们得到一个可观察的值:
这个值可以通过事件改变:
现在您可以在每次此值更改时执行一些代码块:
传递给的函数
ko.computed
将在每次其依赖项发生变化时被调用。PS 上面的代码是作为具有可观察值的手动工作的示例给出的。但请记住,Knockout 有更简单的方法来处理 DOM 元素的内容:
方法 3.1 - MobX 中的可观察对象
这里的一切几乎和淘汰赛一样。在下面的示例中,我使用 ES2016 和更旧的语法,因为该库暗示使用新的语言功能:
然而,MobX 通常使用类而不是单个可观察对象:
我们等了!ES2017 第 8 版。
添加了带有 modifier
async
和 use的函数的描述await
该示例已在 chrome 中运行:
ES2015
这个标准引入了生成器函数的概念——一个可以从中间转移控制然后返回到同一个地方的函数。通常它们用于获取序列
此函数返回可迭代序列的迭代器
1,2,3,3,3,...
。虽然这本身很有趣,但有一个特殊情况。如果结果序列是一系列动作而不是数字,我们可以在每次运行动作时暂停函数并等待结果再返回函数。因此,我们得到的不是数字序列,而是未来值序列:即 承诺。
这有点复杂,但是一个非常强大的技巧允许我们在同步模式下编写异步代码。有几个“启动器”可以做到这一点。例如,我将使用
Promise.coroutine
from Bluebird,但还有其他打包程序,例如со
orQ.async
。此方法还返回可在其他协程中使用的承诺。例如:
ES2016 (ES7) 不远的将来引入新关键字的标准中有一些提示可以更轻松
async
地await
处理 promises。但目前,这些只是保留字,是否会进入下一个标准以及何时会有实施还不得而知。
现在,您可以使用像Babel这样的汇编器来使用它们。
这个答案的部分翻译
可以使用替代 JS 引擎nsynjs同步执行具有异步功能的代码
如果异步函数返回一个承诺
然后我们就调用这个函数,我们通过属性获取 promise 的值
data
:如果异步函数调用回调
第 1 步。我们将异步函数包装在 nsynjs 包装器中(或在 promise 中):
Шаг 2. Помещаем логику в функцию, как если бы логика исполнялась синхронно
Шаг 3. Исполняем функцию через nsynjs
Nsynjs будет последовательно исполнять код функции, останавливаясь и дожидаясь результата вызовов всех аснихронных функций.