Как вернуть значение из события или из функции обратного вызова? Или хотя бы дождаться их окончания
1,00
р.
р.
Пытаюсь делать вот так, но ничего не получается: 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 - переместить присваивание внутрь Возможно, этот способ выглядит как-то глупо - но он решает задачу и наиболее прост в понимании. Если ваше приложение достаточно простое - то так и надо делать. Смотрите: 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+ приводит к коду, который называют "макаронным": // модуль 1 function getResult() { $.get("someapi", function (data) { setResult(data.foo) }) } // модуль 2 function someFunc() { getResult() } function setResult(result) { someDiv.textContent = result } Обращаю внимание: someFunc вызывает getResult, которая вызывает setResult. В итоге два модуля вызывают друг друга. Это и есть макаронный код. Для борьбы с таким кодом и предназначены способы ниже. Способ 1 - обратные вызовы ("колбеки", callbacks) Добавим той функции, которая делает запрос, параметр 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 - обещания ("промизы", promises) Обещание в js - это шаблон программирования, обозначающий значение, которого сейчас нет, но предполагается, что оно будет в будущем. Имеется несколько реализаций обещаний. Основной сейчас являются ES6 Promises, они поддерживаются современными браузерами кроме IE. (Но для тех браузеров, которые их не поддерживают, есть куча полифилов). Создаются обещания вот так: 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 }) Или же можно использовать новый синтаксис async/await, описанный в ответе ниже от Grundy Обращаю внимание, что здесь я для примера взял именно some.api.call, но не событие или ajax-вызов - и это не случайно! Дело в том, что обещание может быть выполнено (resolved) только 1 раз, а большинство событий происходят несколько раз. Поэтому использовать обещания для того же onchanged - нельзя. Что же до 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 Обычно про 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) <!-- бывший someInput --> <!-- бывший someDiv --> Способ 3.1 - наблюдаемые значения (observables) в MobX Тут все почти так же, как и в knockout. В примере ниже я использую синтаксис ES2016 и старше, потому что библиотека подразумевает использование новых средств языка: import { observable, autorun } from 'mobx' var result = observable("") someInput.onchange = () => { result.set(someInput.value) } autorun(() => someDiv.textContent = result.get()) Однако, обычно в MobX используются классы, а не одиночные obervable: class ViewModel { @observable result = "" } var vm = new ViewModel() someInput.onchange = () => { vm.result = someInput.value } autorun(() => someDiv.textContent = vm.result)