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 returning 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).
Related
I have a class, with methods... one of these actually contains a fetch to get an svg file. Nothing really fancy there.
The problem actually doesn't lie in the class, but when using it.
I have a method getDataFromFile(url), I pass it my SVG url and it does a bunch of stuff. The problem is... When I use it, if I call another method right after it, or even just a console.log... As it hasn't yet fecthed the file, I get "undefined" where it should show an object.
Of course if I set a timeout, it works, but that means that everything after it has to be in a timeout... I could also very well set a timeout in the method called after it, but then this one would become asynchronous too.
I've tried bunch of things, and I don't really get promises and stuff alike so... I'm totally stuck there!
Here's a (very) simplified version of my code, I know it's far from optimal (I'm a begginer) but here it is anyway:
var ParentClass = function ()
{
// Attributes and stuff
this.paths = [];
}
var MyClass = function ()
{
ParentClass.call( this );
}
MyClass.prototype.getInlineDOMdata = function ( selector )
{
// Stuff going on...
let querySelector = document.querySelectorAll( selector + " path" );
for ( let i = 0; i < querySelector.length; i++ )
{
this.paths.push(
{
name: querySelector[ i ].id,
color: querySelector[ i ].style.fill,
pathData: querySelector[ i ].attributes.d.value
}
);
}
}
MyClass.prototype.getInlineData = function ( inlineCode )
{
let domTarget = document.querySelector( "body" );
domTarget.innerHTML += `<div class="placeholder">${ inlineCode }<div>`;
let domContainer = document.querySelector( ".placeholder" );
// Stuff going on...
this.getInlineDOMdata( ".placeholder svg" );
domContainer.remove();
}
MyClass.prototype.getDataFromFile = function ( url )
{
fetch( url )
.then( response => response.text() )
.then( data => this.getInlineData( data ));
}
Program side it looks like that :
document.addEventListener( "DOMContentLoaded", loadComplete );
function loadComplete ()
{
var test = new MyClass();
test.getInlineData( `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 195 82">
<!--Some SVG code -->
</svg>
` );
console.log( test.paths[ 0 ] ); // Object { name... }
console.log( test.paths.length ); // 4
test.getInlineDOMdata( "svg" );
console.log( test.paths[ 0 ] ); // Object { name... }
console.log( test.paths.length ); // 4 */
test.getDataFromFile( "https://upload.wikimedia.org/wikipedia/en/c/ce/SVG-logo.svg" );
console.log( test.paths[ 0 ] ); // undefined
console.log( test.paths.length ); // 0
setTimeout( function ()
{
console.log( test.paths[ 0 ] ); // Object { name... }
console.log( test.paths.length ); // 4
}, 1000 );
}
So when I use getInlineData, it works.
When I use getInlineDOMdata, it works.
When I use getDataFromFile, it doesn't! But if I put a timer, it does.
I find that really "dirty" and I'm desperate to find a proper solution where I could simply call any other method directly bellow this one!
EDIT : SOLVED!
With the use of async/await, just changing my method getDataFromFile to
MyClass.prototype.getDataFromFile = async function ( url )
{
let response = await fetch( url )
.then( response => response.text() );
await this.getInlineData( response );
}
and program side, adding async in front of my loadComplete function, and await in front of my test.getDataFromFile( "urlToSvg.svg" ); line solved my issue!
Thanks to Leftium for the great tutarial he provided (tutorial), which made me finally get how promises work
Use async/await. These keywords are basically syntactic sugar for promises, so understanding how promises work will be very helpful.
This is a great tutorial that walks through an async example using promises, then converts it to async/await syntax. Relevant quotes:
The good news is that JavaScript allows you to write
pseudo-synchronous code to describe asynchronous computation. An async
function is a function that implicitly returns a promise and that can,
in its body, await other promises in a way that looks synchronous.
...
Inside an async function, the word await can be put in front of an
expression to wait for a promise to resolve and only then continue the
execution of the function.
Such a function no longer, like a regular JavaScript function, runs
from start to completion in one go. Instead, it can be frozen at any
point that has an await, and can be resumed at a later time.
In my Meteor.methods I have
var post= Posts.insert({...}, function(err,docsInserted){
Posts.update({...} , {...});
});
I want to create an insert model as suggested by David Weldon here.
My model looks like this:
_.extend(Posts, {
ins:function (docID){
return Posts.insert({...});
}
});
In my methods I have this:
var post= Posts.ins(docID, function(err,docsInserted){
Posts.update({...} , {...});
});
How can I use a callback and error handling in the method? I'd like to be able to execute code when the Post is inserted successfully.
Looking at the documentation for collection.insert:
Insert a document in the collection. Returns its unique _id.
Arguments
doc Object
The document to insert. May not yet have an _id attribute, in which case Meteor will generate one for you.
callback Function
Optional. If present, called with an error object as the first argument and, if no error, the _id as the second.
As I understand it, you want to execute a callback function if ins executes successfully. Given that information, here's how I'd structure the code:
_.extend(Posts, {
ins:function (docID, callback){
Posts.insert({...}, function( error, id ) {
if( error ) {
callback( error, null );
} else {
callback( null, id );
}
});
}
});
You don't actually need to return anything. You can just execute the callback function and pass parameters along appropriately. Then you can call the function with the following code:
Posts.ins(docID, function(err,docsInserted){
if( error ) {
// Handle error.
} else {
Posts.update({...} , {...});
}
});
I got a loop like this:
for ( var current in all )
{
//load the item
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
});
}
function AllItemsLoaded()
{
}
My goal is to execute AllItemsLoaded() after all items are loaded and the code in the callbacks is executed, e.g. For every item callback should be called and DoSomethingWithResult() should be executed before AllItemsLoaded() is called, all these items are loaded asynchronously.
I've tried Jquery Deferred/pipe and my code looked like this:
var chain = new $.Deferred().resolve();
for ( var current in all )
{
chain = chain.pipe(function(res){
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
});
});
//if I do a return here, the pipe will continue without getting the result,
so I need to continue the pipe after load's callback and
doSomethingWithResult is executed
}
chain.done(AllItemsLoaded);
Deferred is a good idea. However, you need to wait on the promise. Here's a method using when to wait on all the promises without doing them in order:
var loads = [];
for ( var current in all )
{
(function(){
var deferred = new $.Deferred();
prepare.load( all[current].resource , function( result ) {
doSomethingWithResult(result);
deferred.resolve(result);
});
loads.push(deferred.promise());
})();
}
$.when.apply(null, loads).then(AllItemsLoaded);
First create a new deferred for each load. Place its promise in a collection. After the load, resolve the deferred. Wait on all of the loads with $.when().
Is this what you need?
From: http://aabs.wordpress.com/2009/12/16/sequential-script-loading-on-demand/
function LoadScriptsSequentially(scriptUrls, callback)
{
if (typeof scriptUrls == 'undefined') throw "Argument Error: URL array is unusable";
if (scriptUrls.length == 0 && typeof callback == 'function') callback();
$.getScript(scriptUrls.shift(), function() { LoadScriptsSequentially(scriptUrls, callback); });
}
I would approach it as such (below), where you replace each $.get() with your own asynchronous object, with it's own individual complete handler.
$(document).ready(function() {
$.when(
$.get("ajax.php?a=b"),
$.get("ajax.php?a=c"),
$.get("ajax.php?a=d")
).then(
function() {
// both AJAX calls have succeeded
alert("All Done");
},
function() {
// one of the AJAX calls has failed
alert("One or more failed");
}
);
});
First thing is to use .get() or .post() not .load(), the reason being that .load() returns jQuery while the other two return jqXHR (ie a promise), which is what you want here.
Next thing is to provide an array in which to accumulate the jqXHR promises.
Last you need to know how to get $.when() to act on the array of promises, to do something when all of them are resolved (or an error occurs).
The whole thing looks like this :
var promises = [];//new Array
for ( var current in all ) {
prepare.get( all[current].resource, function( result ) {
doSomethingWithResult(result);
});
}
$.when.apply(null, promises).then(AllItemsLoaded, myErrorHandler);
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 9 years ago.
I have the following jQuery Function. I'm trying to return the GUID value shown here in the alert(); The alert works fine and the value is populated, however I can't seem to assign it to a variable and return its value.
Ultimately I need to access the GUID value in other functions, etc. Everything I've tried only displays as undefined.
I'd like to do something like this:
function trackPage(){
var elqTracker = new jQuery.elq(459);
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function(guid) {
alert(guid);
var returnValue = guid;
});
}
});
return returnValue;
}
var someGuid = trackPage();
So, this question has been asked a million times over, and I'm sure that everyone (myself included) tried this once.
It is just the nature of an asynchronous call, you can't use their results as a return value. Thats why they have you passing in a function that gets the result of the call, they can't return it either! Also notice that the elqTracker.pageTrack() function call returns IMMEDIATELY, therefore your returnValue is simply undefined.
Most people (see dfsq's answer) solve this problem by introducing a callback function as a paramater. This method is tried, and true – however jQuery has $.Deferred. This allows you to make your own asynchronous logic return a promise which you can then attach any number of callbacks to:
function trackPage(){
var elqTracker = new jQuery.elq( 459 ),
dfd = $.Deferred();
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function( guid ) {
dfd.resolve( guid );
});
}
});
return dfd.promise();
}
// example use:
trackPage().done(function( guid ) {
alert( "Got GUID:" + guid );
});
Notice now that your trackPage() returns an object that you can attach callbacks to? You don't have to attach them immediately either.
var pageHit = trackPage().done(function( guid ) {
alert( "Page Hit GUID:" +guid );
});
$("button").click(function() {
pageHit.done( function( guid ) {
alert( "Clicked on Page GUID:" + guid );
});
});
Also, the jQuery AJAX module always returns promises as well, so the interface for all your AJAX stuff should be very similar if you make your own logic return promises.
As a side note: I'd like to point out that your var returnValue was in the wrong "scope" anyway. It needed to be declared in the outer scope of the trackPage function. Even with this fix, the concept still doesn't work.
Since you have asynchronous call the way you are trying to write code is not going to work (because by the moment of return returnValue; in the trackCode return value is not yet defined). Instead you should pass callback into trackPage:
function trackPage(callback) {
var elqTracker = new jQuery.elq(459);
elqTracker.pageTrack({
success: function() {
elqTracker.getGUID(function(guid) {
alert(guid);
// Instead of this: var returnValue = guid;
// You should use your callback function
callback(guid);
});
}
});
return returnValue;
}
trackCode(function(guid) {
// perform some actions with guid
});
An array of functions, [fn1,fn2,...], each "returns" through a callback, passing an optional error. If an error is returned through the callback, then subsequent functions in the array should not be called.
// one example function
function fn1( callback ) {
<process>
if( error ) callback( errMsg );
else callback();
return;
}
// go through each function until fn returns error through callback
[fn1,fn2,fn3,...].forEach( function(fn){
<how to do this?>
} );
This can be solved other ways, but nonetheless would love the syntactic dexterity to use approach.
Can this be done?
as per correct answer:
[fn1,fn2,fn3,...].every( function(fn) {
var err;
fn.call( this, function(ferr) { err = ferr; } );
if( err ) {
nonblockAlert( err );
return false;
}
return true;
} );
seems this has room for simplification.
for me, much better approach to solve this type of problem - it's flatter, the logic more accessible.
If I understand your question correctly and if you can use JavaScript 1.6 (e.g. this is for NodeJS), then you could use the every function.
From MDN:
every executes the provided callback function once for each element
present in the array until it finds one where callback returns a false
value. If such an element is found, the every method immediately
returns false. Otherwise, if callback returned a true value for all
elements, every will return true.
So, something like:
[fn1, fn2, fn3, ...].every(function(fn) {
// process
if (error) return false;
return true;
});
Again, this requires JavaScript 1.6