I am trying to make a model (the M in MVC) in my web application, to understand the concept. I am struggling with the async problem that occurs in my code below:
function FruitModel(api) {
this._api = api;
}
FruitModel.prototype = {
getFruit: function(fruit_id) {
$.getJSON(this._api + fruit_id, function(data){
return data;
});
}
}
$(function(){
var fruits = new FruitModel("/fruit/");
console.log(fruits.getFruit(123))
});
I get that this is wrong because the console.log will happen before the getJSON has finished, be cause of that this is asynchronous. How should I design this instead to prevent the problem?
I suppose that getting the actual data for a front end MVC is to access it from within the model from a RESTapi equal to mine. Is there a preferred solution instead of doing this inside the model as I do?
You can send a callback to the function and execute it when you done with your async code:
FruitModel.prototype = {
getFruit: function(fruit_id,callback) {
$.getJSON(this._api + fruit_id, function(data){
callback(data);
});
}
}
$(function(){
var fruits = new FruitModel("/fruit/");
fruits.getFruit(123, function(data){
console.log(data);
});
});
To avoid that problem you can do something like this ( because from jQuery 1.5 .getJSON implements Promise interface )
function FruitModel(api) {
this._api = api;
}
FruitModel.prototype = {
getFruit: function(fruit_id) {
return $.getJSON(this._api + fruit_id);
}
}
//Call
fruits.getFruit(123).done(function(data){
console.log(data);
})
Related
I'm trying to send a "large" table in OfficeJS:
functionfile.html loaded from manifest route
<script>
(function (){
"use strict";
Office.initialize = function (reason) {
$(document).ready(function() {
$("#send-data-button").click(send_data);
});
};
function send_data() {
return Excel.run( function(context) {
var data = context.workbook.worksheets.getItem("SheetName")
.getRange("A1:K3673").load("values");
return context.sync().then( function() {
// 2d table is correctly seen
// $("body").append(data.values);
// Just gets lost in ajax call
$.ajax({
type: "GET",
url: mysite,
data: {"accessData": data.values},
}).done( function(success) {
$("body").append("All Done");
}).fail( function(error) {
$("body").append("Error == " + JSON.stringify(error));
});
return context.sync();
});
});
}
})();
</script>
<div> <button id="send-data-button"> Send </button></div>
However i'm not sure how to send this, on the backside I have a flask server catching the request and was hoping I could just use pandas.read_json but no matter how I try to send this i'm getting different errors. Here's the printout of flask.request when data.values[0][0]:
CombinedMultiDict([ImmutableMultiDict([('update_date', '43191'), ('accessData', 'Channel')]), ImmutableMultiDict([])])
And when I try data.values[0] I get a list of values, which is what i'd expect
CombinedMultiDict([ImmutableMultiDict([('update_date', '43191'), ('accessData[]', 'Channel'), ... <All my column headers>, ImmutableMultiDict([])])
But when I try to send the 2D array with just data.values I get an error message in ajax.fail:
Error == {"readyState":0,"status":0,"statusText":"error"}
I also tried JSON.stringify(data.values) and got the same error message:
Error == {"readyState":0,"status":0,"statusText":"error"}
I even tried to take each column and convert them to some kind of list as nested keys inside accessData but I was getting the same error message. Any help would be greatly appreciated.
Ideally, you should isolate the getting-data-from-Excel part from your ajax call part. Right now, the two are intertwined, which makes it both harder to help debug, and just conceptually less clean.
For the Excel part, you should be able to do:
function getExcelData(){
return Excel.run( function(context) {
var data = context.workbook.worksheets.getItem("SheetName")
.getRange("A1:K3673").load("values");
return context.sync()
.then(function() {
return data.values;
});
})
}
This will free you up to then do:
getExcelData().then(function(values) {
$.ajax(...)
});
Note that range.values returns just a regular 2D array, nothing special. So you can try out your ajax call independently of the Excel call (which is yet another reason to separate those out)
communicating with a REST service in ionic, I'ld like to have functions similar to this
function ListCategories_Request(){
rqst=_BuildRequest("ListCategories");
rqst.Data={extraParam1:1,
extraParam2:2
}
return $http({...ValidParameters including the rqst...}).then(GetResult,Request_onError)
}
For each function of the REST-Service, I would build a similar java-script function.
Now, as the sent request is unique, the received result needs to be handled unique, too. The REST-Service-Based analysis of the result is added to the .then-chain, but updating the parent class and the UI needs to happen in the parent class.
So, I would like to do calls like
ListCategories_Request().then(function(res){ UpdateCategories()});
ListFrames_Request().then(function(res){ UpdateFrames()});
The current problem is, that UpdateCategories() is called, before the result of the http-Request is analysed.
So, how do I prevent return $http(...).then(GetResult,OnError) to return, before the specific function inside GetResult is called?
Code of GetResult:
function GetResult( res){
if(res.status==200)
{
if( (res.data!=={}) && (res.data.Data!=={}) )
{
return AnalyzeResult(res.data);
}
}
};
While AnalyzeResult is like:
function AnalyzeResult(Result)
{
Func=Result.Func;
switch(Func.toUpperCase())
{
case "LISTCATEGORIES":
erg = ListCategories_Result(Result);
break;
case "LISTFRAMES":
erg= ListFrames_Result(Result);
break;
default:
erg = {};
console.log("UnKnown Result!");
}
FinalizeRequest(Index);
}
return erg;
}
So, I do not really get, where my mistake is. How do I prevent ListCategories_Request or ListFrames_Request from returning too early?
Thank you and best regards
Frank
You are better off using $q (given you are using angular anyway via ionic). That way you can use promises to do what you want to do. Here's an example that I put together to demonstrate the idea:
var app = angular.module("TestApp",[]);
app.controller("TestController", function($scope, $http, $q){
$scope.message = "Deferred Example";
var deferred = $q.defer();
function getAllPosts(extractor) {
$http.get("https://jsonplaceholder.typicode.com/posts")
.then(function(data){
console.log("Data is: ", data);
$scope.postIds = extractor(data.data);
deferred.resolve($scope.postIds);
});
return deferred.promise;
}
function extractPostIds(data) {
console.log("Exracting the data: ", data);
return data.map(function(post){
return post.id;
})
}
function squarePostIds(postIds) {
console.log("Squaring post ids: ", postIds);
$scope.squaredPostIds = postIds.map(function(id){ return id*id;});
}
getAllPosts(extractPostIds)
.then(function(postIds){
squarePostIds(postIds);
});
});
And here's the JSBin for this: https://jsbin.com/fugeyad/3/edit?html,js,output
Update: Adding some links to read about promises.
http://andyshora.com/promises-angularjs-explained-as-cartoon.html
http://www.html5rocks.com/en/tutorials/es6/promises/
I am trying to read data from json and wait until data will be fetched into $scope.urls.content. So I write code:
$scope.urls = { content:null};
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
});
And now I am trying to write something like callback but that doesn't work. How can i do that? Or is there any function for this? I am running out of ideas ;/
Do you mean that ?
$http.get('mock/plane_urls.json').success(function(thisData) {
$scope.urls.content = thisData;
$scope.yourCallback();
});
$scope.yourCallback = function() {
// your code
};
You want to work with promises and $resource.
As $http itself returns a promise, all you got to do is to chain to its return. Simple as that:
var promise = $http.get('mock/plane_urls.json').then(function(thisData) {
$scope.urls.content = thisData;
return 'something';
});
// somewhere else in the code
promise.then(function(data) {
// receives the data returned from the http handler
console.log(data === "something");
});
I made a pretty simple fiddle here.
But if you need to constantly call this info, you should expose it through a service, so anyone can grab its result and process it. i.e.:
service('dataService', function($http) {
var requestPromise = $http.get('mock/plane_urls.json').then(function(d) {
return d.data;
});
this.getPlanesURL = function() {
return requestPromise;
};
});
// and anywhere in code where you need this info
dataService.getPlanesURL().then(function(planes) {
// do somehting with planes URL
$scope.urls.content = planes;
});
Just an important note. This service I mocked will cache and always return the same data. If what you need is to call this JSON many times, then you should go with $resource.
I have a function that accepts a callback function where I pass the data back in. Can this converted to a deferred object for better practice?
Here is what I got:
var chapters;
var getChapters = function (fnLoad) {
//CACHE DATA IF APPLICABLE
if (!chapters) {
//CALL JSON DATA VIA AJAX
$.getJSON('/chapters.txt')
.done(function (json) {
//STORE DATA IN LOCAL STORAGE
chapters = Lawnchair(function () {
this.save(json, function (data) {
//CALL CALLBACK ON DATA
fnLoad(data);
});
});
});
} else {
//RETURN ALREADY CREATED LOCAL STORAGE
chapters.all(function (data) {
//CALL CALLBACK ON DATA
fnLoad(data);
});
}
};
Then I simply use it like this:
this.getChapters(function (data) {
console.log(data);
});
How can I use it like a promise though while maintaining the cache approach?
this.getChapters().done(function (data) {
console.log(data);
});
var chapters;
var getChapters = function (fnLoad) {
var d = new $.Deferred();
//CACHE DATA IF APPLICABLE
if (!chapters) {
//CALL JSON DATA VIA AJAX
$.getJSON('/chapters.txt')
.done(function (json) {
//STORE DATA IN LOCAL STORAGE
chapters = Lawnchair(function () {
this.save(json, function (data) {
//CALL CALLBACK ON DATA
d.resolve(data);
});
});
})
.fail(function() { d.reject(); });
} else {
//RETURN ALREADY CREATED LOCAL STORAGE
chapters.all(function (data) {
//CALL CALLBACK ON DATA
d.resolve(data);
});
}
return d.promise();
};
Relevant example
I see you have already accepted an answer, however if you take a large mental leap and store a promise of chapters instead of the chapters themselves, then the code will simplify significantly.
These days, this is probably the more generally adopted approach for a "fetch/cache" situation.
var chapters_promise;
var getChapters = function () {
//Cache data if applicable and return promise of data
if (!chapters_promise)
chapters_promise = $.getJSON('/chapters.txt').then(Lawnchair).then(this.save);
return chapters_promise;
};
What is actually promised (the chapters) will be determined by the value(s) returned by the functions Lawnchair and this.save, so you still have some work to do.
getChapters() will always return a promise, regardless of whether the data needs to be fetched or is already cached. Therefore, getChapters() can only be used with promise methods .then(), .done(), .fail() or .always(), for example :
getChapters().then(fnLoad);
You have no other way to access the chapters but that is reasonable since at any call of getChapters(), you don't know whether it will follow the $.getJSON() branch or the simple return branch, both of which return an identical promise.
Totally new to OOP in javascript, but Im trying and reading all I can.
Ive created a simple test javascript class called Invoices. Invoices only has two methods. One method fires the other. And this part seems to be working fine.
My problem lies with getting data objects from one method to another. I put a alert in the first method, (from my understanding) this alert should show data returned from the second method... but its not.
Any help would be greatly appreciated. Oh.. and I am using jquery as well.
Here is my codez.
function Invoices()
{
this.siteURL = "http://example.com/";
this.controllerURL = "http://example.com/invoices/";
this.invoiceID = $('input[name="invoiceId"]').val();
}
invoice = new Invoices;
Invoices.prototype.initAdd = function()
{
//load customers json obj
this.customersJSON = invoice.loadCustomers();
alert(this.customersJSON);
//create dropdown
}
Invoices.prototype.loadCustomers = function ()
{
$.post(this.controllerURL + "load_customers"),
function(data)
{
return data;
}
}
There are two problems with that. First of all, $.post is asynchronous; you'll have to adopt a callback scheme or use $.ajax to make it synchronous. Secondly, you probably meant to do this:
$.post(this.controllerURL + "load_customers", function(data) {
return data;
});
Note how the closure is in the parentheses of the function call.
As you were told the AJAX call is asynchronous, you would have to implement your initAdd in 2 step:
BeginInitAdd which would initiate the AJAX call
EndInitAdd which would be the callback for your AJAX call and perform the action depending on the data returned.
Invoices.prototype.initAdd = function()
{
//load customers json obj
this.xhrObj = invoice.loadCustomers();
alert(this.customersJSON);
}
Invoices.prototype.createDropdown = function (data) {
//create dropdown
this.customersJSON=data
}
Invoices.prototype.loadCustomers = function ()
{
return $.post(this.controllerURL + "load_customers"),
function(data)
{
//return data;
this.createDropdown(data)
}
}