У меня есть функция foo, которая делает запрос Ajax. Как я могу вернуть ответ от foo?

Я попытался вернуть значение из обратного вызова success, а также присвоить ответ локальной переменной внутри функции и вернуть ее, но ни один из этих способов фактически не возвращает ответ.

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            result = response;
            // return response; // <- I tried that one as well
        }
    });

    return result;
}

var result = foo(); // It always ends up being `undefined`.
5383
Felix Kling 8 Янв 2013 в 21:06

27 ответов

Большинство ответов здесь дают полезные советы для случая, когда у вас есть одна асинхронная операция, но иногда это происходит, когда вам нужно выполнить асинхронную операцию для каждой записи в массиве или другой структуре, подобной списку. , Соблазн сделать это:

// WRONG
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log(results); // E.g., using them, returning them, etc.

Примере:

// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
    doSomethingAsync(entry, function(result) {
        results.push(result);
    });
});
console.log("Results:", results); // E.g., using them, returning them, etc.

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Причина, по которой это не работает, заключается в том, что обратные вызовы от doSomethingAsync еще не выполнялись к тому времени, когда вы пытаетесь использовать результаты.

Таким образом, если у вас есть массив (или какой-то список) и вы хотите выполнять асинхронные операции для каждой записи, у вас есть два варианта: выполнять операции параллельно (с наложением) или последовательно (одна за другой в последовательности).

Параллельно

Вы можете запустить их все и отследить, сколько обратных вызовов вы ожидаете, а затем использовать результаты, когда вы получили столько обратных вызовов:

var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

Примере:

var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
    doSomethingAsync(entry, function(result) {
        results[index] = result;
        if (--expecting === 0) {
            // Done!
            console.log("Results:", results); // E.g., using the results
        }
    });
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Мы могли бы покончить с expecting и просто использовать results.length === theArray.length, но это оставляет нас открытым для вероятности того, что theArray будет изменен, пока вызовы ожидают ...)

Обратите внимание, как мы используем index из forEach, чтобы сохранить результат в results в той же позиции, что и запись, к которой он относится, даже если результаты приходят не в порядке (так как асинхронный вызов don не обязательно завершать в том порядке, в котором они были начаты).

Но что, если вам нужно вернуть эти результаты из функции? Как указали другие ответы, вы не можете; вам нужно, чтобы ваша функция принимала и вызывала обратный вызов (или возвращала

Примере:

function doSomethingWith(theArray, callback) {
    var results = [];
    var expecting = theArray.length;
    theArray.forEach(function(entry, index) {
        doSomethingAsync(entry, function(result) {
            results[index] = result;
            if (--expecting === 0) {
                // Done!
                callback(results);
            }
        });
    });
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

Или вот версия, возвращающая Promise вместо этого:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Конечно, если doSomethingAsync передал нам ошибки, мы бы использовали reject, чтобы отклонить обещание, когда мы получили ошибку.)

Примере:

function doSomethingWith(theArray) {
    return new Promise(function(resolve) {
        var results = [];
        var expecting = theArray.length;
        theArray.forEach(function(entry, index) {
            doSomethingAsync(entry, function(result) {
                results[index] = result;
                if (--expecting === 0) {
                    // Done!
                    resolve(results);
                }
            });
        });
    });
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Или вы можете создать оболочку для doSomethingAsync, которая возвращает обещание, а затем выполнить следующее ...)

Если doSomethingAsync дает вам Обещание вы можете использовать Promise.all :

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(function(entry) {
        return doSomethingAsync(entry);
    }));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Если вы знаете, что doSomethingAsync будет игнорировать второй и третий аргумент, вы можете просто передать его непосредственно map (map вызывает его обратный вызов с тремя аргументами, но большинство людей используют только первый, наиболее времени):

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Примере:

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Обратите внимание, что Promise.all разрешает свое обещание с массивом результатов всех обещаний, которые вы даете ему, когда все они разрешены, или отклоняет свое обещание, когда первое из обещаний, которые вы ему даете отвергает .

Серии

Предположим, вы не хотите, чтобы операции были параллельными? Если вы хотите запускать их один за другим, вам нужно дождаться завершения каждой операции, прежде чем начинать следующую. Вот пример функции, которая делает это и вызывает обратный вызов с результатом:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith(theArray, function(results) {
    console.log("Results:", results);
});

(Поскольку мы выполняем работу последовательно, мы можем просто использовать results.push(result), поскольку знаем, что не получим результаты не в порядке. В приведенном выше примере мы могли бы использовать results[index] = result;, но в некоторых из следующих примеров у нас нет индекса для использования.)

Примере:

function doSomethingWith(theArray, callback) {
    var results = [];
    doOne(0);
    function doOne(index) {
        if (index < theArray.length) {
            doSomethingAsync(theArray[index], function(result) {
                results.push(result);
                doOne(index + 1);
            });
        } else {
            // Done!
            callback(results);
        }
    }
}
doSomethingWith([1, 2, 3], function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value, callback) {
    console.log("Starting async operation for " + value);
    setTimeout(function() {
        console.log("Completing async operation for " + value);
        callback(value * 2);
    }, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
  max-height: 100% !important;
}

(Или, опять же, создайте оболочку для doSomethingAsync, которая даст вам обещание и выполните следующее ...)

Если doSomethingAsync дает вам Обещание, если вы можете использовать синтаксис ES2017 + (возможно, с помощью транспилятора, такого как Babel), вы можно использовать async функцию с for-of и await:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Примере:

async function doSomethingWith(theArray) {
    const results = [];
    for (const entry of theArray) {
        results.push(await doSomethingAsync(entry));
    }
    return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

Если вы не можете использовать синтаксис ES2017 + (пока), вы можете использовать вариант " Promise Reduce " (это более сложный вариант, чем обычное сокращение Promise, потому что мы не передаем результат из одного в другое, а собираем их результаты в массив):

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Примере:

function doSomethingWith(theArray) {
    return theArray.reduce(function(p, entry) {
        return p.then(function(results) {
            return doSomethingAsync(entry).then(function(result) {
                results.push(result);
                return results;
            });
        });
    }, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}

... что менее громоздко с ES2015 + функциями стрелок :

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
    console.log("Results:", results);
});

Примере:

function doSomethingWith(theArray) {
    return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
        results.push(result);
        return results;
    })), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
    console.log("Results:", results);
});

function doSomethingAsync(value) {
    console.log("Starting async operation for " + value);
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("Completing async operation for " + value);
            resolve(value * 2);
        }, Math.floor(Math.random() * 200));
    });
}
.as-console-wrapper {
  max-height: 100% !important;
}
140
T.J. Crowder 26 Апр 2019 в 07:50

Вы используете 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);
    }
});

Возврат чего-либо в обработчик отправки ничего не сделает. Вместо этого вы должны либо передать данные, либо сделать то, что вы хотите, непосредственно внутри функции успеха.

238
Peter Mortensen 21 Ноя 2015 в 14:07

Это очень распространенная проблема, с которой мы сталкиваемся, борясь с «загадками» JavaScript. Позвольте мне попытаться раскрыть эту тайну сегодня.

Давайте начнем с простой функции JavaScript:

function foo(){
// do something 
 return 'wohoo';
}

let bar = foo(); // bar is 'wohoo' here

Это простой синхронный вызов функции (где каждая строка кода «заканчивается своей работой» перед следующей последовательной), и результат такой же, как и ожидалось.

Теперь давайте добавим немного поворота, введя небольшую задержку в нашу функцию, чтобы все строки кода не были «закончены» в последовательности. Таким образом, он будет эмулировать асинхронное поведение функции:

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

Итак, вы идете, эта задержка просто сломала функциональность, которую мы ожидали! Но что именно произошло? Ну, это на самом деле довольно логично, если вы посмотрите на код. функция foo() после выполнения ничего не возвращает (таким образом, возвращаемое значение равно undefined), но она запускает таймер, который выполняет функцию через 1 с, чтобы вернуть 'wohoo'. Но, как вы можете видеть, значение, назначенное для bar, - это немедленно возвращаемый материал из foo (), а не что-либо еще, что будет позже.

Итак, как нам решить эту проблему?

Давайте попросим нашу функцию ОБЕЩАТЬ . Обещание действительно о том, что оно означает: это означает, что функция гарантирует, что вы предоставите любой вывод, который она получит в будущем. Итак, давайте посмотрим на это в действии для нашей маленькой проблемы выше:

function foo(){
   return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
    setTimeout ( function(){ 
      // promise is RESOLVED , when execution reaches this line of code
       resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
    }, 1000 )
  })
}

let bar ; 
foo().then( res => {
 bar = res;
 console.log(bar) // will print 'wohoo'
});

Таким образом, краткое изложение - для решения асинхронных функций, таких как вызовы на основе ajax и т. Д., Вы можете использовать обещание resolve значения (которое вы намереваетесь вернуть). Таким образом, короче говоря, вы разрешаете значение вместо возврата в асинхронных функциях.

ОБНОВЛЕНИЕ (обещания с асинхронным ожиданием)

Помимо использования then/catch для работы с обещаниями, существует еще один подход. Идея состоит в том, чтобы распознать асинхронную функцию , а затем дождаться разрешения обещаний , прежде чем переходить к следующей строке кода. Это все еще просто promises под капотом, но с другим синтаксическим подходом. Чтобы прояснить ситуацию, вы можете найти сравнение ниже:

Затем / поймать версию:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
         throw err;
       })
 }

Версия async / await:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        throw err;
     }
  }
67
Anish K. 24 Сен 2019 в 10:40

Используя Promise

Самый совершенный ответ на этот вопрос - использование Promise.

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

Но ждать...!

Существует проблема с использованием обещаний!

Почему мы должны использовать наши собственные обещания?

Я использовал это решение некоторое время, пока не выяснил, есть ли ошибка в старых браузерах:

Uncaught ReferenceError: Promise is not defined

Поэтому я решил реализовать свой собственный класс Promise для ES3 ниже js компиляторов, если он не определен. Просто добавьте этот код перед вашим основным кодом, а затем смело используйте Promise!

if(typeof Promise === "undefined"){
    function _classCallCheck(instance, Constructor) {
        if (!(instance instanceof Constructor)) { 
            throw new TypeError("Cannot call a class as a function"); 
        }
    }
    var Promise = function () {
        function Promise(main) {
            var _this = this;
            _classCallCheck(this, Promise);
            this.value = undefined;
            this.callbacks = [];
            var resolve = function resolve(resolveValue) {
                _this.value = resolveValue;
                _this.triggerCallbacks();
            };
            var reject = function reject(rejectValue) {
                _this.value = rejectValue;
                _this.triggerCallbacks();
            };
            main(resolve, reject);
        }
        Promise.prototype.then = function then(cb) {
            var _this2 = this;
            var next = new Promise(function (resolve) {
                _this2.callbacks.push(function (x) {
                    return resolve(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.catch = function catch_(cb) {
            var _this2 = this;
            var next = new Promise(function (reject) {
                _this2.callbacks.push(function (x) {
                    return reject(cb(x));
                });
            });
            return next;
        };
        Promise.prototype.triggerCallbacks = function triggerCallbacks() {
            var _this3 = this;
            this.callbacks.forEach(function (cb) {
                cb(_this3.value);
            });
        };
        return Promise;
    }();
}
22
Amir Forsati 28 Май 2019 в 10:08

Короткий ответ: вы должны реализовать обратный вызов следующим образом:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});
78
Pablo Matias Gomez 9 Авг 2016 в 18:32

Я отвечу ужасно выглядящим, нарисованным от руки комиксом. Второе изображение - причина, по которой result является undefined в вашем примере кода.

enter image description here

208
Johannes Fahrenkrug 11 Авг 2016 в 17:12

Это одно из мест, где привязка данных или концепция хранения , которые используются во многих новых JavaScript-фреймворках, отлично подойдет для вас ...

Поэтому, если вы используете Angular, React или любые другие фреймворки, которые используют привязку данных двумя способами или хранилище , эта проблема просто решается для вас, проще говоря, ваш результат - undefined на первом этапе, поэтому у вас есть result = undefined до получения данных, затем, как только вы получите результат, он будет обновлен и назначен на новое значение, ответ которого на ваш вызов Ajax ...

Но как вы можете сделать это в чистом javascript или jQuery , например, как вы задали в этом вопросе?

Вы можете использовать обратный вызов , обещание и недавно наблюдаемый , чтобы обработать его для вас, например, в обещаниях у нас есть некоторая функция, такая как {{X0} } или then(), которые будут выполнены, когда ваши данные будут готовы для вас, то же самое с обратным вызовом или функцией подписки для наблюдаемой .

Например, в вашем случае, который вы используете 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(); //call happens here
});

Для получения дополнительной информации изучите обещания и наблюдаемые , которые являются новыми способами сделать это асинхронным.

96
Alireza 23 Сен 2019 в 03:14

Краткий ответ . Ваш метод foo() немедленно возвращается, а вызов $ajax() выполняется асинхронно после возврата функции . Тогда проблема заключается в том, как или где хранить результаты, полученные асинхронным вызовом после его возврата.

В этой теме было дано несколько решений. Возможно, самый простой способ - передать объект в метод foo() и сохранить результаты в элементе этого объекта после завершения асинхронного вызова.

function foo(result) {
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;   // Store the async result
        }
    });
}

var result = { response: null };   // Object to hold the async result
foo(result);                       // Returns before the async completes

Обратите внимание, что вызов foo() все равно не даст ничего полезного. Однако результат асинхронного вызова теперь будет сохранен в result.response.

36
David R Tribble 23 Сен 2015 в 22:52

Несмотря на то, что обещания и обратные вызовы работают хорошо во многих ситуациях, в тылу трудно выразить что-то вроде:

if (!name) {
  name = async1();
}
async2(name);

В итоге вы пройдете через async1; проверить, является ли name неопределенным или нет, и соответственно вызвать обратный вызов.

async1(name, callback) {
  if (name)
    callback(name)
  else {
    doSomething(callback)
  }
}

async1(name, async2)

Хотя это хорошо в маленьких примерах, это раздражает, когда у вас много подобных случаев и обработка ошибок.

Fibers помогает в решении проблемы.

var Fiber = require('fibers')

function async1(container) {
  var current = Fiber.current
  var result
  doSomething(function(name) {
    result = name
    fiber.run()
  })
  Fiber.yield()
  return result
}

Fiber(function() {
  var name
  if (!name) {
    name = async1()
  }
  async2(name)
  // Make any number of async calls from here
}

Вы можете оформить заказ на проект здесь.

83
rohithpr 9 Май 2016 в 13:02

Посмотрите на этот пример.

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope,$http) {

    var getJoke = function(){
        return $http.get('http://api.icndb.com/jokes/random').then(function(res){
            return res.data.value;  
        });
    }

    getJoke().then(function(res) {
        console.log(res.joke);
    });
});

Как видите, getJoke возвращает решенное обещание (оно разрешается при возврате res.data.value). Поэтому вы ждете, пока запрос $ http.get не будет выполнен, а затем будет выполнен console.log (res.joke) (как обычный асинхронный поток).

Это plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

Способ ES6 (асинхронный - ожидание)

(function(){
  async function getJoke(){
    let response = await fetch('http://api.icndb.com/jokes/random');
    let data = await response.json();
    return data.value;
  }

  getJoke().then((joke) => {
    console.log(joke);
  });
})();
104
Francisco Carmona 23 Ноя 2018 в 12:19

Вот несколько подходов для работы с асинхронными запросами:

  1. Обозреватель Обещание объекта
  2. Q - библиотека обещаний для JavaScript
  3. A + Promises.js
  4. JQuery отложено
  5. API XMLHttpRequest
  6. Использование концепции обратного вызова - как реализация в первом ответе

Пример: отложенная реализация jQuery для работы с несколькими запросами

var App = App || {};

App = {
    getDataFromServer: function(){

      var self = this,
                 deferred = $.Deferred(),
                 requests = [];

      requests.push($.getJSON('request/ajax/url/1'));
      requests.push($.getJSON('request/ajax/url/2'));

      $.when.apply(jQuery, requests).done(function(xhrResponse) {
        return deferred.resolve(xhrResponse.result);
      });
      return deferred;
    },

    init: function(){

        this.getDataFromServer().done(_.bind(function(resp1, resp2) {

           // Do the operations which you wanted to do when you
           // get a response from Ajax, for example, log response.
        }, this));
    }
};
App.init();
38
Mohan Dere 22 Июн 2017 в 09:31

Конечно, есть много подходов, таких как синхронный запрос, обещание, но из моего опыта я думаю, что вы должны использовать подход обратного вызова. Это естественно для асинхронного поведения Javascript. Итак, ваш фрагмент кода может быть переписан немного иначе:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}
28
Michał Perłakowski 7 Мар 2018 в 14:23

Другим решением является выполнение кода через последовательного исполнителя nsynjs.

Если основная функция обещана

Nsynjs будет последовательно оценивать все обещания и помещать результат обещания в свойство 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 (если у нее обещанная версия, вы можете пропустить этот шаг):

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 будет оценивать все операторы и выражения шаг за шагом, приостанавливая выполнение в случае, если результат какой-то медленной функции не готов.

Дополнительные примеры здесь: https://github.com/amaksr/nsynjs/tree/master/examples

62
amaksr 3 Апр 2019 в 16:00

Самое простое решение - создать функцию JavaScript и вызвать ее для обратного вызова Ajax success.

function callServerAsync(){
    $.ajax({
        url: '...',
        success: function(response) {

            successCallback(response);
        }
    });
}

function successCallback(responseObj){
    // Do something like read the response and show data
    alert(JSON.stringify(responseObj)); // Only applicable to JSON response
}

function foo(callback) {

    $.ajax({
        url: '...',
        success: function(response) {
           return callback(null, response);
        }
    });
}

var result = foo(function(err, result){
          if (!err)
           console.log(result);    
}); 
225
clearlight 15 Янв 2017 в 06:17

Используйте callback() функцию внутри foo() успеха. Попробуй таким образом. Это просто и легко понять.

var lat = "";
var lon = "";
function callback(data) {
    lat = data.lat;
    lon = data.lon;
}
function getLoc() {
    var url = "http://ip-api.com/json"
    $.getJSON(url, function(data) {
        callback(data);
    });
}

getLoc();
35
Alex Weitz 17 Ноя 2017 в 09:15

Ответ на 2017 год: теперь вы можете делать именно то, что вы хотите, в каждом текущем браузере и узле

Это довольно просто:

  • Вернуть обещание
  • Используйте 'await', который покажет JavaScript для ожидания обещания быть преобразованным в значение (например, HTTP-ответ)
  • Добавьте ключевое слово async к родительскому элементу. функция

Вот рабочая версия вашего кода:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

await поддерживается во всех текущих браузерах и узле 8

73
mikemaccana 1 Окт 2018 в 10:06

Вы можете использовать эту пользовательскую библиотеку (написанную с помощью Promise) для удаленного вызова.

function $http(apiConfig) {
    return new Promise(function (resolve, reject) {
        var client = new XMLHttpRequest();
        client.open(apiConfig.method, apiConfig.url);
        client.send();
        client.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                // Performs the function "resolve" when this.status is equal to 2xx.
                // Your logic here.
                resolve(this.response);
            }
            else {
                // Performs the function "reject" when this.status is different than 2xx.
                reject(this.statusText);
            }
        };
        client.onerror = function () {
            reject(this.statusText);
        };
    });
}

Простой пример использования:

$http({
    method: 'get',
    url: 'google.com'
}).then(function(response) {
    console.log(response);
}, function(error) {
    console.log(error)
});
65
Peter Mortensen 17 Дек 2016 в 10:59

Вопрос был:

Как вернуть ответ от асинхронного вызова?

Что МОЖЕТ быть интерпретировано как:

Как заставить асинхронный код выглядеть синхронным ?

Решение состоит в том, чтобы избежать обратных вызовов и использовать комбинацию Обещаний и async / await .

Я хотел бы привести пример для запроса Ajax.

(Хотя он может быть написан на Javascript, я предпочитаю писать на Python и скомпилировать его в Javascript, используя Transcrypt . Будет достаточно понятно.)

Давайте сначала включим использование JQuery, чтобы $ был доступен как S:

__pragma__ ('alias', 'S', '$')

Определите функцию, которая возвращает Promise , в данном случае Ajax-вызов:

def read(url: str):
    deferred = S.Deferred()
    S.ajax({'type': "POST", 'url': url, 'data': { },
        'success': lambda d: deferred.resolve(d),
        'error': lambda e: deferred.reject(e)
    })
    return deferred.promise()

Используйте асинхронный код, как если бы он был синхронным :

async def readALot():
    try:
        result1 = await read("url_1")
        result2 = await read("url_2")
    except Exception:
        console.warn("Reading a lot failed")
27
Pieter Jan Bonestroo 24 Май 2018 в 08:36

Используя ES2017, вы должны иметь это как объявление функции

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

И выполнить это так.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Или синтаксис обещания

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})
16
Fernando Carvajal 24 Янв 2018 в 06:18

Js однопоточный.

Браузер можно разделить на три части:

1) Event Loop

2) Веб-API

3) Очередь событий

Цикл событий выполняется вечно, то есть, как бесконечный цикл. Очередь событий - это место, где вся ваша функция помещается в какое-то событие (например, щелчок). Это выполняется одна за другой из очереди и помещается в цикл событий, который выполняет эту функцию и подготавливает ее самостоятельно. для следующего за первым выполняется. Это означает, что выполнение одной функции не начинается до тех пор, пока функция в очереди не будет выполнена в цикле обработки событий.

Теперь давайте подумаем, что мы поместили две функции в очередь: одна предназначена для получения данных с сервера, а другая использует эти данные. Сначала мы поместили функцию serverRequest () в очередь, а затем функцию utiliseData (). Функция serverRequest входит в цикл обработки событий и выполняет вызов к серверу, так как мы никогда не знаем, сколько времени потребуется для получения данных с сервера, поэтому ожидается, что этот процесс займет время, и поэтому мы заняты циклом обработки событий, таким образом, подвисая нашу страницу, вот где Web API вступает в роль, он берет эту функцию из цикла событий и имеет дело с тем, что сервер делает цикл событий свободным, чтобы мы могли выполнить следующую функцию из очереди. Следующая функция в очереди - это utiliseData (), который идет в цикле, но из-за отсутствия доступных данных он идет растрата и выполнение следующей функции продолжается до конца очереди (это называется асинхронным вызовом, т.е. мы можем делать что-то еще, пока не получим данные)

Предположим, что у нашей функции serverRequest () есть код возврата в коде, когда мы получаем данные из серверного веб-API и помещаем их в очередь в конце очереди. Так как он помещается в конец очереди, мы не можем использовать его данные, так как в нашей очереди не осталось функции для использования этих данных. Таким образом, невозможно вернуть что-либо из Async Call.

Таким образом, решением этой проблемы является обратный вызов или обещание .

Изображение из одного из ответов здесь, правильно объясняет использование обратного вызова ... Мы передаем нашу функцию (функцию, использующую данные, возвращенные с сервера) функции, вызывающей сервер.

CallBack

 function doAjax(callbackFunc, method, url) {
  var xmlHttpReq = new XMLHttpRequest();
  xmlHttpReq.open(method, url);
  xmlHttpReq.onreadystatechange = function() {

      if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
        callbackFunc(xmlHttpReq.responseText);
      }


  }
  xmlHttpReq.send(null);

}

В моем кодексе это называется

function loadMyJson(categoryValue){
  if(categoryValue==="veg")
  doAjax(print,"GET","http://localhost:3004/vegetables");
  else if(categoryValue==="fruits")
  doAjax(print,"GET","http://localhost:3004/fruits");
  else 
  console.log("Data not found");
}

Читайте здесь о новых методах в ECMA (2016/17) для выполнения асинхронных вызовов (@Felix Kling Answer on Top) https://stackoverflow.com/a/14220323/7579856

60
Aniket Jha 16 Мар 2018 в 16:48

Другой подход для возврата значения из асинхронной функции - передать объект, который будет хранить результат из асинхронной функции.

Вот пример того же:

var async = require("async");

// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
    // some asynchronous operation
    $.ajax({
        url: '...',
        success: function(response) {
            result.response = response;
            _callback();
        }
    });
});

async.parallel(asyncTasks, function(){
    // result is available after performing asynchronous operation
    console.log(result)
    console.log('Done');
});

Я использую объект result для хранения значения во время асинхронной операции. Это позволяет результату быть доступным даже после асинхронного задания.

Я часто использую этот подход. Мне было бы интересно узнать, насколько хорошо работает этот подход, когда используется передача результатов через последовательные модули.

92
Peter Mortensen 17 Дек 2016 в 12:55

Angular1

Люди, которые используют AngularJS, могут справиться с этой ситуацией, используя Promises.

Здесь говорится:

Обещания могут использоваться для отмены размещения асинхронных функций и позволяют объединять несколько функций в цепочку.

вы также можете найти здесь хорошее объяснение.

Пример найден в документах, упомянутых ниже.

  promiseB = promiseA.then(
    function onSuccess(result) {
      return result + 1;
    }
    ,function onError(err) {
      //Handle error
    }
  );

 // promiseB will be resolved immediately after promiseA is resolved 
 // and its value will be the result of promiseA incremented by 1.

Angular2 и позже

В Angular2 рассмотрим следующий пример, но его рекомендуется использовать Observables с Angular2.

 search(term: string) {
     return this.http
  .get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
  .map((response) => response.json())
  .toPromise();

}

Вы можете потреблять это таким образом,

search() {
    this.searchService.search(this.searchField.value)
      .then((result) => {
    this.result = result.artists.items;
  })
  .catch((error) => console.error(error));
}

Смотрите оригинальный пост здесь. Но Typescript не поддерживает нативные обещания es6, если вы хотите его использовать, вам может понадобиться плагин для этого.

Кроме того, здесь приведены спецификации обещания.

151
Maleen Abewardana 6 Июл 2017 в 04:45

Вместо того, чтобы бросать на вас код, есть две концепции, которые являются ключевыми для понимания того, как JS обрабатывает обратные вызовы и асинхронность. (Это вообще слово?)

Модель цикла и параллелизма событий

Есть три вещи, о которых вам нужно знать; очередь; цикл событий и стек

В общих чертах, цикл событий похож на менеджера проектов, он постоянно прослушивает любые функции, которые хотят запустить, и обменивается данными между очередью и стеком.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Как только он получает сообщение для запуска чего-либо, он добавляет его в очередь. Очередь - это список вещей, которые ожидают выполнения (например, ваш AJAX-запрос). представьте это так:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Когда одно из этих сообщений будет выполнено, оно извлекает сообщение из очереди и создает стек, а стек - это все, что JS должен выполнить для выполнения инструкции в сообщении. Так что в нашем примере ему говорят: foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

Поэтому все, что нужно выполнить foobarFunc (в нашем случае anotherFunction), будет помещено в стек. выполнено, а затем забыто - цикл событий переместится на следующую вещь в очереди (или прослушает сообщения)

Ключевым моментом здесь является порядок исполнения. Это

КОГДА-то будет работать

Когда вы делаете вызов с использованием AJAX внешней стороне или запускаете какой-либо асинхронный код (например, setTimeout), Javascript зависит от ответа, прежде чем он сможет продолжить.

Большой вопрос, когда он получит ответ? Ответ на этот вопрос - мы не знаем - поэтому цикл обработки событий ожидает, пока сообщение не скажет «эй, беги меня». Если JS только что ждал этого сообщения синхронно, ваше приложение зависло бы, и оно было бы отстойным. Таким образом, JS продолжает выполнение следующего элемента в очереди, ожидая, когда сообщение будет добавлено в очередь.

Вот почему с асинхронной функциональностью мы используем вещи, называемые обратными вызовами . Это что-то вроде обещания в буквальном смысле. Как и в я обещаю вернуть что-то в какой-то момент , jQuery использует специальные обратные вызовы, называемые deffered.done deffered.fail и deffered.always (среди прочих). Вы можете увидеть их все здесь

Итак, вам нужно передать функцию, которую обещают выполнить в какой-то момент, с данными, которые ему передают.

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

function foo(bla) {
  console.log(bla)
}

Поэтому большую часть времени (но не всегда) вы будете проходить foo, а не foo()

Надеюсь, в этом есть смысл. Когда вы сталкиваетесь с такими вещами, которые кажутся запутанными - я настоятельно рекомендую прочитать документацию полностью, чтобы хотя бы понять ее. Это сделает вас намного лучшим разработчиком.

17
Matthew Brent 4 Май 2018 в 15:56

Мы попадаем во вселенную, которая, кажется, движется в измерении, которое мы называем «время». Мы не очень понимаем, что такое время, но мы разработали абстракции и словарный запас, которые позволяют нам рассуждать и говорить об этом: «прошлое», «настоящее», «будущее», «до», «после».

Компьютерные системы, которые мы создаем - все больше и больше - имеют время как важное измерение. Определенные вещи созданы, чтобы произойти в будущем. Тогда другие вещи должны произойти после того, как эти первые вещи в конце концов произойдут. Это основное понятие называется «асинхронность». В нашем мире, который становится все более сетевым, наиболее распространенным случаем асинхронности является ожидание того, что удаленная система ответит на какой-либо запрос.

Рассмотрим пример. Вы звоните молочнику и заказываете немного молока. Когда это приходит, вы хотите положить его в свой кофе. Вы не можете положить молоко в свой кофе прямо сейчас, потому что его еще нет. Вы должны подождать, пока оно придет, прежде чем положить его в свой кофе. Другими словами, следующее не будет работать:

var milk = order_milk();
put_in_coffee(milk);

Потому что у JS нет возможности узнать, что ему нужно ждать , чтобы order_milk завершил работу, прежде чем он выполнит put_in_coffee. Другими словами, он не знает, что order_milk является асинхронным - это то, что не приведет к появлению молока до некоторого будущего времени. JS и другие декларативные языки выполняют один оператор за другим без ожидания.

Классический подход JS к этой проблеме, использующий преимущество того факта, что JS поддерживает функции как объекты первого класса, которые могут быть переданы, состоит в том, чтобы передать функцию в качестве параметра асинхронному запросу, который он затем вызовет после завершения. его задача когда-нибудь в будущем. Это подход "обратного вызова". Это выглядит так:

order_milk(put_in_coffee);

order_milk начинает, заказывает молоко, затем, когда и только когда оно прибывает, оно вызывает put_in_coffee.

Проблема с этим подходом обратного вызова состоит в том, что он загрязняет нормальную семантику функции, сообщающей о своем результате с return; вместо этого функции не должны сообщать о своих результатах, вызывая функцию обратного вызова, заданную в качестве параметра. Кроме того, этот подход может быстро стать громоздким при работе с более длинными последовательностями событий. Например, предположим, что я хочу подождать, пока молоко будет добавлено в кофе, а затем и только тогда выполнить третий шаг, а именно выпить кофе. В итоге мне нужно написать что-то вроде этого:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

Где я передаю put_in_coffee и молоко для его добавления, а также действие (drink_coffee), которое нужно выполнить после того, как молоко введено. Такой код становится трудно писать, читать и отладки .

В этом случае мы могли бы переписать код в вопросе как:

var answer;
$.ajax('/foo.json') . done(function(response) {
  callback(response.data);
});

function callback(data) {
  console.log(data);
}

Введите обещания

Это послужило мотивацией для понятия «обещание», которое представляет собой особый тип значения, представляющий некоторый будущий или асинхронный результат. Он может представлять собой то, что уже произошло, или произойдет в будущем, или может вообще никогда не произойти. Обещания имеют единственный метод, названный then, которому вы передаете действие, которое будет выполнено, когда результат, который представляет обещание, будет реализован.

В случае нашего молока и кофе мы планируем order_milk возвращать обещание на прибытие молока, затем указываем put_in_coffee как действие then следующим образом:

order_milk() . then(put_in_coffee)

Одним из преимуществ этого является то, что мы можем связать их вместе, чтобы создать последовательности будущих вхождений («цепочка»):

order_milk() . then(put_in_coffee) . then(drink_coffee)

Давайте применим обещания к вашей конкретной проблеме. Мы обернем нашу логику запроса внутри функции, которая возвращает обещание:

function get_data() {
  return $.ajax('/foo.json');
}

На самом деле все, что мы сделали, это добавили return к вызову $.ajax. Это работает, потому что jQuery $.ajax уже возвращает что-то вроде обещания. (На практике, не вдаваясь в подробности, мы бы предпочли обернуть этот вызов так, чтобы он возвращал реальное обещание, или использовать некоторую альтернативу $.ajax, которая делает это.) Теперь, если мы хотим загрузить файл и ждать для того, чтобы закончить, а затем сделать что-то, мы можем просто сказать,

get_data() . then(do_something)

Например,

get_data() . 
  then(function(data) { console.log(data); });

При использовании обещаний мы в конечном итоге передаем множество функций в then, поэтому часто полезно использовать более компактные функции стрелок в стиле ES6:

get_data() . 
  then(data => console.log(data));

Ключевое слово async

Но все еще есть что-то смутно недовольное в том, что нужно писать код одним способом, если он синхронный, и другим способом, если он асинхронный. Для синхронного мы пишем

a();
b();

Но если a является асинхронным, с обещаниями мы должны написать

a() . then(b);

Выше мы говорили: «JS не может знать, что ему нужно ждать , чтобы первый вызов завершился, прежде чем он выполнит второй». Разве не было бы хорошо, если бы был каким-то образом сказать JS об этом? Оказывается, есть ключевое слово await, используемое внутри специального типа функции, называемой «асинхронной» функцией. Эта функция является частью будущей версии ES, но она уже доступна в таких транспортерах, как Babel, с правильными настройками. Это позволяет нам просто написать

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

В вашем случае вы сможете написать что-то вроде

async function foo() {
  data = await get_data();
  console.log(data);
}
33
4 revs, 3 users 92%user663031 10 Дек 2018 в 05:52

В следующем примере, который я написал, показано, как

  • Обрабатывать асинхронные HTTP-вызовы;
  • Дождаться ответа от каждого вызова API;
  • Используйте шаблон Promise;
  • Используйте шаблон Promise.all, чтобы присоединиться несколько HTTP-вызовов;

Этот рабочий пример самодостаточен. Он определит простой объект запроса, который использует объект окна XMLHttpRequest для выполнения вызовов. Он определит простую функцию, которая будет ждать выполнения обещаний.

Контекст . Пример запрашивает конечную точку Spotify Web API, чтобы найти {{ X0}} объекты для заданного набора строк запроса:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Для каждого элемента новое Обещание будет запускать блок - ExecutionBlock, анализировать результат, планировать новый набор обещаний на основе массива результатов, то есть списка объектов Spotify user, и выполнять новый HTTP-вызов внутри ExecutionProfileBlock асинхронно.

Затем вы можете увидеть вложенную структуру Promise, которая позволяет создавать множественные и полностью асинхронные вложенные HTTP-вызовы и объединять результаты каждого подмножества вызовов через Promise.all.

< Сильный > Примечание Для API-интерфейсов последних Spotify search потребуется указать токен доступа в заголовках запроса:

-H "Authorization: Bearer {your access token}" 

Итак, чтобы запустить следующий пример, вам нужно поместить свой токен доступа в заголовки запроса:

var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
    log: function(s) {
        document.getElementById("console").innerHTML += s + "<br/>"
    }
}

// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
    call: function(what, response) {
        var request;
        if (window.XMLHttpRequest) { // Mozilla, Safari, ...
            request = new XMLHttpRequest();
        } else if (window.ActiveXObject) { // Internet Explorer
            try {
                request = new ActiveXObject('Msxml2.XMLHTTP');
            }
            catch (e) {
                try {
                  request = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {}
            }
        }

        // State changes
        request.onreadystatechange = function() {
            if (request.readyState === 4) { // Done
                if (request.status === 200) { // Complete
                    response(request.responseText)
                }
                else
                    response();
            }
        }
        request.open('GET', what, true);
        request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
        request.send(null);
    }
}

//PromiseAll
var promiseAll = function(items, block, done, fail) {
    var self = this;
    var promises = [],
                   index = 0;
    items.forEach(function(item) {
        promises.push(function(item, i) {
            return new Promise(function(resolve, reject) {
                if (block) {
                    block.apply(this, [item, index, resolve, reject]);
                }
            });
        }(item, ++index))
    });
    Promise.all(promises).then(function AcceptHandler(results) {
        if (done) done(results);
    }, function ErrorHandler(error) {
        if (fail) fail(error);
    });
}; //promiseAll

// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
    var url = "https://api.spotify.com/v1/"
    url += item;
    console.log( url )
    SimpleRequest.call(url, function(result) {
        if (result) {

            var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
                return item.owner.href;
            })
            resolve(profileUrls);
        }
        else {
            reject(new Error("call error"));
        }
    })
}

arr = [
    "search?type=playlist&q=%22doom%20metal%22",
    "search?type=playlist&q=Adele"
]

promiseAll(arr, function(item, index, resolve, reject) {
    console.log("Making request [" + index + "]")
    ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results

    console.log("All profiles received " + results.length);
    //console.log(JSON.stringify(results[0], null, 2));

    ///// promiseall again

    var ExecutionProfileBlock = function(item, index, resolve, reject) {
        SimpleRequest.call(item, function(result) {
            if (result) {
                var obj = JSON.parse(result);
                resolve({
                    name: obj.display_name,
                    followers: obj.followers.total,
                    url: obj.href
                });
            } //result
        })
    } //ExecutionProfileBlock

    promiseAll(results[0], function(item, index, resolve, reject) {
        //console.log("Making request [" + index + "] " + item)
        ExecutionProfileBlock(item, index, resolve, reject);
    }, function(results) { // aggregated results
        console.log("All response received " + results.length);
        console.log(JSON.stringify(results, null, 2));
    }

    , function(error) { // Error
        console.log(error);
    })

    /////

  },
  function(error) { // Error
      console.log(error);
  });
<div id="console" />

Я подробно обсудил это решение

loretoparisi 28 Ноя 2018 в 16:42

Если вы не используете 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'

Феликс Клинг отлично справился с задачей, написав ответ для людей, использующих jQuery для AJAX. Я решил предоставить альтернативу тем, кто этого не делает.

(Обратите внимание, что для тех, кто использует новый fetch API, Angular или обещания, я добавил другой ответ ниже)


С чем вы сталкиваетесь

Это краткое изложение «Объяснение проблемы» из другого ответа, если вы не уверены, прочитав это, прочитайте это.

A в AJAX означает асинхронный . Это означает, что отправка запроса (или, вернее, получение ответа) исключается из обычного потока выполнения. В вашем примере .send немедленно возвращается, и следующий оператор return result; выполняется до того, как функция, которую вы передали в качестве success обратного вызова, была даже вызвана.

Это означает, что когда вы возвращаете, определенный слушатель еще не выполнялся, то есть возвращаемое вами значение не было определено.

Вот простая аналогия

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

( Fiddle )

Возвращаемое значение 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 действие, которое нужно выполнить после его завершения, мы сообщаем нашему коду, как реагировать на завершение события (например, наш вызов AJAX или в этом случае тайм-аут).

Использование будет:

getFive(onComplete);

Который должен предупредить «5» на экране. (Fiddle).

Возможные решения

Есть два основных способа решения этой проблемы:

  1. Сделайте вызов AJAX синхронным (назовем его AJAX).
  2. Реструктурируйте ваш код для правильной работы с обратными вызовами.

1. Синхронный AJAX - не делай этого !!

Что касается синхронного AJAX, не делай этого! ответ Феликса вызывает некоторые убедительные аргументы о том, почему это плохая идея. Подводя итог, он замораживает браузер пользователя до тех пор, пока сервер не вернет ответ и не создаст очень плохой пользовательский опыт. Вот еще одно краткое изложение 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 может быть сделано для принятия обратного вызова. Мы расскажем нашему коду, как реагировать , когда foo завершится.

Так:

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

Для получения более подробной информации о том, как делается этот дизайн обратного вызова, проверьте ответ Феликса.

Теперь давайте определим сам 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 и действуя соответствующим образом (создайте обработчик ошибок и т. Д.). Эффективно решаем наш вопрос.

Если вам все еще трудно понять это руководство по началу работы с AJAX у мдн.

1035
whackamadoodle3000 1 Мар 2018 в 01:03