While putting to practice what I've learned so far about ES2015 with Babel, specifically about WeakMaps, I came upon a problem I don't know why it's not working.
I have a WeakMap defined to house data coming from an AJAX call that's only triggered if the value of said WeakMap is undefined
.
This is what I came up to:
class User {
constructor( id ) {
id = Number( id );
if( id <= 0 || isNaN( id ) ) {
throw new TypeError( 'Invalid User ID' );
}
_id.set( this, id );
}
getID() {
return _id.get( this );
}
getData() {
let _this = this;
if( _data.get( _this ) === undefined ) {
_this.loadData().done( function( data ) {
// JSON is indeed successfully loaded
console.log( data );
_data.set( _this, data );
// WeakMap is indeed set correctly
console.log( _data.get( _this ) );
});
}
// But here it's undefined again!
console.log( _data.get( _this ) );
return _data.get( _this );
}
loadData() {
return $.get({
url: '/users/' + _id.get( this, data ),
});
}
}
let _id = new WeakMap;
let _data = new WeakMap;
// ---------------
var user = new User( 1 );
console.log( user.getID(), user.getData() ); // 1 undefined
As far as i know, I am setting the WeakMap data correctly, as the User ID is being set and can be retrieved, but the User Data coming from AJAX, although is indeed being set inside the jQuery.done() can't be accessed outside of it.
what i'm doing wrong?
I don't understand JavaScript to the point saying this is the right solution or not but I've searched A LOT, reading countless questions here in Stack Overflow with immense answers that fails to put things ins simple ways so, for anyone interested:
class User {
constructor( id ) {
id = Number( id );
if( id <= 0 || isNaN( id ) ) {
throw new TypeError( 'Invalid User ID' );
}
_id.set( this, id );
}
getID() {
return _id.get( this );
}
getData() {
if( _data.get( this ) === undefined ) {
_data.set( this, this.loadData() );
}
return _data.get( this );
}
loadData() {
return $.getJSON( CARD_LIST + '/player/' + _id.get( this ) );
}
}
let _data = new WeakMap;
// ---------------
var user = new User( 1 );
user.getData().done( function( data ) {
console.log( data );
})
It's not what I had in mind initially and I don't have knowledge to explain the "whys" but, at least this is a palpable working example that I humbly hope that will helps someone else who's trying to extract info from extremely long answers and/or unhelpful/unguided comments.
See How do I return the response from an asynchronous call? for a (lengthy) primer of how to use the result of an asynchronous operation like Ajax.
The short answer is: you cannot use an asynchronously-fetched result if you're synchronously return
ing a value. Your getData
function returns before any Ajax call resolves, so the synchronous return value of getData
cannot depend upon any asynchronously-fetched value. This isn't even a matter of "waiting" long enough: the basic principle of JavaScript event handling is that the current function stack (i.e., the current function, and the function that directly called that function, etc.) must resolve entirely before any new events are handled. Handling an asynchronous Ajax result is a new event, so your done
handler necessarily will not run until well after your getData
function execution is ancient history.
The two general ways to solve your issue are:
Make the Ajax call synchronous
Make the Ajax-fetched value accessible asynchronously outside of getData
You can do #2 by either passing a callback into getData
, like
getData(funnction(data) { console.log("If this is running, we finally got the data", data); }
function getData(callback) {
_this.loadData.done(function(data) {
_data.set(this, data);
// etc. etc.
callback(_data.get( _this ));
});
}
So, now the getData
is itself asynchronus, does this force you to rewrite any use of `getData you already have, and cause propagation of asynchronous patterns all over your code? Yes, it does.
If you want to use return a promises instead, you could do
getData().then(function(data) {
console.log("Got the data", data);
});
function getData() {
return _this.loadData().then(function(data) {
_data.set(this, data);
// etc. etc.
return _data.get( _this );
});
}
this works because promises allowing chaining then
calls. This simply chains then
inside and outside the get data function: one then
callback is queued inside getData
, and the next is queued outside getData
. The return value of the first callback is used as the argument of the second. The return value of then
is the original promise, so I've simply used the form return mypromise.then(...)
, which returns the same object as if I'd just done return mypromise
(but obviously I haven't set up a callback in that case).