#If##If you're using promises, this answer is for you.
This means AngularJS, jQuery (with deferred), native XHR'sXHR's replacement (fetch), EmberJSEmber.js, BackboneJS'sBackbone.js's save or any nodeNode.js library that returns promises.
function foo() {
var data;
// orOr $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result'result' is always undefined no matter what.
Felix Kling did a fine job writing an answer for people using jQuery with callbacks for AJAXAjax. I have an answer for native XHR. This answer is for generic usage of promises either on the frontend or backend.
#The##The core issue
The JavaScript concurrency model in the browser and on the server with NodeJSNode.js/io.js is asynchronous and reactive.
Here is a simple analogy for the issue:
function getFive(){
var data;
setTimeout(function(){ // setSet a timer for one second in the future
data = 5; // afterAfter a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
The value of data
is undefined
since the data = 5
part has not executed yet. It will likely execute in a second, but by that time it is irrelevant to the returned value.
Since the operation did not happen yet (AJAXAjax, server call, IOI/O, and timer) you're returning the value before the request got the chance to tell your code what that value is.
#Quick###Quick recap on promises
A Promise is a value over time. Promises have state, they. They start as pending with no value and can settle to:
A promise can only change states once after which it will always stay at the same state forever. You can attach then
handlers to promises to extract their value and handle errors. then
handlers allow chaining of calls. Promises are created by using APIs that return them. For example, the more modern AJAXAjax replacement fetch
or jQuery's $.get
return promises.
With promises
###With promises
function delay(ms){ // takesTakes amount of milliseconds
// returnsReturns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // whenWhen the time is up,
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
Now, after we converted setTimeoutconverted setTimeout to use promises, we can use then
to make it count:
function delay(ms){ // takesTakes amount of milliseconds
// returnsReturns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // whenWhen the time is up,
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we'reWe're RETURNING the promise,. rememberRemember, a promise is a wrapper over our value
return delay(100).then(function(){ // whenWhen the promise is ready,
return 5; // return the value 5,. promisesPromises are all about return values
})
}
// weWe _have_ to wrap it like this in the call site, and we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // processProcess it inside the `then`
});
}
foo().then(function(response){
// accessAccess the value inside the `then`
})
So this works just as well. We've learned we can't return values from already asynchronous calls, but we can use promises and chain them to perform processing. We now know how to return the response from an asynchronous call.
#ES2015##ES2015 (ES6)
function* foo(){ // noticeNotice the star,. thisThis is ES6, so new browsers/node/, Nodes.js, and io.js only
yield 1;
yield 2;
while(true) yield 3;
}
Is a function that returns an iterator over the sequence 1,2,3,3,3,3,....
which can be iterated. While this is interesting on its own and opens room for a lot of possibility, there is one particular interesting case.
This somewhat a tricky, but very powerful trick letslet’s us write asynchronous code in a synchronous manner. There are several "runners" that do this for you, writing. Writing one is a short few lines of code, but it is beyond the scope of this answer. I'll be using Bluebird's Promise.coroutine
here, but there are other wrappers like co
or Q.async
.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // noticeNotice the yield
// The code here only executes _after_ the request is done
return data.json(); // data'data' is defined
});
var main = coroutine(function*(){
var bar = yield foo(); // waitWait our earlier coroutine,. itIt returns a promise
// The server call is done here, and the code below executes when done
var baz = yield fetch("/api/users/"+bar" + bar.userid); // dependsDepends on foo's result
console.log(baz); // runsRuns after both requests are done
});
main();
#ES2016##ES2016 (ES7)
In ES7, this is further standardized, there. There are several proposals right now, but in all of them you can await
promise. This is just "sugar" (nicer syntax) for the ES6 proposal above by adding the async
and await
keywords. Making the above example:
async function foo(){
var data = await fetch("/echo/json"); // noticeNotice the await
// code here only executes _after_ the request is done
return data.json(); // data'data' is defined
}