javascript - How do I return the response from an asynchronous call? -
i have function foo
makes ajax request. how can return response foo
?
i tried return value success
callback assigning response local variable inside function , return one, none of ways return response.
function foo() { var result; $.ajax({ url: '...', success: function(response) { result = response; // return response; // <- tried 1 } }); return result; } var result = foo(); // ends being `undefined`.
->
more general explanation of async behavior different examples, please see why variable unaltered after modify inside of function? - asynchronous code reference
->
if understand problem, skip possible solutions below.
the problem
the a in ajax stands asynchronous . means sending request (or rather receiving response) taken out of normal execution flow. in example, $.ajax
returns , next statement, return result;
, executed before function passed success
callback called.
here analogy makes difference between synchronous , asynchronous flow clearer:
synchronous
imagine make phone call friend , ask him you. although might take while, wait on phone , stare space, until friend gives answer needed.
the same happening when make function call containing "normal" code:
function finditem() { var item; while(item_not_found) { // search } return item; } var item = finditem(); // item dosomethingelse();
even though finditem
might take long time execute, code coming after var item = finditem();
has wait until function returns result.
asynchronous
you call friend again same reason. time tell him in hurry , should call back on mobile phone. hang up, leave house , whatever planned do. once friend calls back, dealing information gave you.
that's what's happening when ajax request.
finditem(function(item) { // item }); dosomethingelse();
instead of waiting response, execution continues , statement after ajax call executed. response eventually, provide function called once response received, callback (notice something? call back ?). statement coming after call executed before callback called.
solution(s)
embrace asynchronous nature of javascript! while asynchronous operations provide synchronous counterparts (so "ajax"), it's discouraged use them, in browser context.
why bad ask?
javascript runs in ui thread of browser , long running process lock ui, making unresponsive. additionally, there upper limit on execution time javascript , browser ask user whether continue execution or not.
all of bad user experience. user won't able tell whether working fine or not. furthermore, effect worse users slow connection.
in following @ 3 different solutions building on top of each other:
- promises
async/await
(es2017+, available in older browsers if use transpiler or regenerator) - callbacks (popular in node)
- promises
then()
(es2015+, available in older browsers if use 1 of many promise libraries)
all 3 available in current browsers, , node 7+.
es2017+: promises async/await
the new ecmascript version released in 2017 introduced syntax level support asynchronous functions. of async
, await
, can write asynchronous in "synchronous style". make no mistake though: code still asynchronous, it's easier read/understand.
async/await
builds on top of promises: async
function returns promise. await
"unwraps" promise , either result in value promise resolved or throws error if promise rejected.
important: can use await
inside async
function. means @ top level, still have work directly promise.
you can read more async
, await
on mdn.
here example builds on top of delay above:
// using 'superagent' return promise. var superagent = require('superagent') // isn't declared `async` because returns promise function delay() { // `delay` returns promise return new promise(function(resolve, reject) { // `delay` able resolve or reject promise settimeout(function() { resolve(42); // after 3 seconds, resolve promise value 42 }, 3000); }); } async function getallbooks() { try { // list of book ids of current user var bookids = await superagent.get('/user/books'); // wait second (just sake of example) await delay(1000); // information each book return await superagent.get('/books/ids='+json.stringify(bookids)); } catch(error) { // if of awaited promises rejected, catch block // catch rejection reason return null; } } // async functions return promise getallbooks() .then(function(books) { console.log(books); });
newer browser , node versions support async/await
. can support older environments transforming code es5 of regenerator (or tools use regenerator, such babel).
let functions accept callbacks
a callback function passed function. other function can call function passed whenever ready. in context of asynchronous process, callback called whenever asynchronous process done. usually, result passed callback.
in example in question, can make foo
accept callback , use success
callback. this
var result = foo(); // code depends on 'result'
becomes
foo(function(result) { // code depends on 'result' });
here defined function "inline" can pass function reference:
function mycallback(result) { // code depends on 'result' } foo(mycallback);
foo
defined follows:
function foo(callback) { $.ajax({ // ... success: callback }); }
callback
refer function pass foo
when call , pass on success
. i.e. once ajax request successful, $.ajax
call callback
, pass response callback (which can referred result
, since how defined callback).
you can process response before passing callback:
function foo(callback) { $.ajax({ // ... success: function(response) { // example, filter response callback(filtered_response); } }); }
it's easier write code using callbacks may seem. after all, javascript in browser heavily event-driven (dom events). receiving ajax response nothing else event.
difficulties arise when have work third-party code, problems can solved thinking through application flow.
es2015+: promises then()
the promise api new feature of ecmascript 6 (es2015), has browser support already. there many libraries implement standard promises api , provide additional methods ease use , composition of asynchronous functions (e.g. bluebird).
promises containers future values. when promise receives value (it resolved) or when canceled (rejected), notifies of "listeners" want access value.
the advantage on plain callbacks allow decouple code , easier compose.
here simple example of using promise:
function delay() { // `delay` returns promise return new promise(function(resolve, reject) { // `delay` able resolve or reject promise settimeout(function() { resolve(42); // after 3 seconds, resolve promise value 42 }, 3000); }); } delay() .then(function(v) { // `delay` returns promise console.log(v); // log value once resolved }) .catch(function(v) { // or else if rejected // (it not happen in example, since `reject` not called). });
applied our ajax call use promises this:
function ajax(url) { return new promise(function(resolve, reject) { var xhr = new xmlhttprequest(); xhr.onload = function() { resolve(this.responsetext); }; xhr.onerror = reject; xhr.open('get', url); xhr.send(); }); } ajax("/echo/json") .then(function(result) { // code depending on result }) .catch(function() { // error occurred });
describing advantages promise offer beyond scope of answer, if write new code, should consider them. provide great abstraction , separation of code.
more information promises: html5 rocks - javascript promises
side note: jquery's deferred objects
deferred objects jquery's custom implementation of promises (before promise api standardized). behave promises expose different api.
every ajax method of jquery returns "deferred object" (actually promise of deferred object) can return function:
function ajax() { return $.ajax(...); } ajax().done(function(result) { // code depending on result }).fail(function() { // error occurred });
side note: promise gotchas
keep in mind promises , deferred objects containers future value, not value itself. example, suppose had following:
function checkpassword() { return $.ajax({ url: '/password', data: { username: $('#username').val(), password: $('#password').val() }, type: 'post', datatype: 'json' }); } if (checkpassword()) { // tell user they're logged in }
this code misunderstands above asynchrony issues. specifically, $.ajax()
doesn't freeze code while checks '/password' page on server - sends request server , while waits, returns jquery ajax deferred object, not response server. means if
statement going deferred object, treat true
, , proceed though user logged in. not good.
but fix easy:
checkpassword() .done(function(r) { if (r) { // tell user they're logged in } else { // tell user password bad } }) .fail(function(x) { // tell user bad happened });
not recommended: synchronous "ajax" calls
as mentioned, some(!) asynchronous operations have synchronous counterparts. don't advocate use, completeness' sake, here how perform synchronous call:
without jquery
if directly use xmlhttprequest
object, pass false
third argument .open
.
jquery
if use jquery, can set async
option false
. note option deprecated since jquery 1.8. can either still use success
callback or access responsetext
property of jqxhr object:
function foo() { var jqxhr = $.ajax({ //... async: false }); return jqxhr.responsetext; }
if use other jquery ajax method, such $.get
, $.getjson
, etc., have change $.ajax
(since can pass configuration parameters $.ajax
).
heads up! not possible make synchronous jsonp request. jsonp nature asynchronous (one more reason not consider option).
Comments
Post a Comment