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 results 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. 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 cancelled (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 promises 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

Popular posts from this blog

twig - Using Twigbridge in a Laravel 5.1 Package -

Kivy: Swiping (Carousel & ScreenManager) -

jdbc - Not able to establish database connection in eclipse -