Javascript infinite scrolling & multiple AJAX Promises - javascript

I am trying to implement infinite scrolling in my app. So I want to make multiple AJAX calls as user scrolls. So I want to get a new set of records from server on scroll. I have an action 'infiniteScrolled' which gets invoked by my custom component on scrolling.
My question is when I try to invoke this.get('handleGridPromise') again, it does not get fired again (probably because the promise was resolved by the 1st call)
How can I fix the same ?
handleGridPromise: function() {
var self = this;
var request = self.get('serverParams');
return new Ember.RSVP.Promise(function(resolve, reject){
var promise = self.updateRequest(request);
promise.then(function(updateRequest){
var deferred = self.doXhrPost(updateRequest, self.get('gridURL'), true, false);
deferred.then(function(response){
self.gridDataLoaded(response);
resolve(self.get('model.gridData'));
});
});
});
}.property('attr1', 'attr2'),
infiniteScrolled(record, index) {
Ember.set(this.get('serverParams'), 'recordstartindex', index);
Ember.set(this.get('serverParams'), 'recordendindex', index+50);
this.get('handleGridPromise').then((records) => {
const until = Math.min((index + 50), (this.get('model.gridData.length') - 1));
Ember.run.later(() => {
for (index; index < until; index++) {
if (records[index].id === undefined) {
records.replace(index, 1, this.get(`model.gridData.${index}`));
}
}
}, 500);
});
}

Related

javascript - Promise.all freezes UI

I am modifying one plugin to add a new feature to it. The plugin uses Promise.all as below:
Promise.all([
coreUtils.promisify(_(this).fetch, this.buildRequest()),
fetchDataIds.call(this)
])
.then(this.onFetchSuccess.bind(this))
.catch(this.onFetchError.bind(this));
This works fine without adding a support for the new feature.
Ok that's fine. But when I add multi-selectbox as a part of new feature, I've to call Promise.all each time item is selected in selectbox. This works fine sometimes and sometimes not. The UI freezes mostly when I select/deselect items in selectbox quickly. If I do in slow pace (in normal speed), it works fine.
Update:
The async method is (_(this).fetch):
fetch: function (request) {
return usersService.get(request);
},
function mockGet(request) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
var start = request.offset,
end = start !== undefined ? request.offset + request.limit : undefined,
sortDirection = request.sortDir === 'asc' ? 1 : -1,
responseData = data;
// logic to filter and sort data goes here
var res = {
items: start !== undefined ? responseData.slice(start, end) : responseData,
total: responseData.length
};
resolve(coreUtils.clone(res, true));
}, 500);
});
}
buildRequest: function () {
return {
sortAttr: this.getSortAttribute(),
sortDir: this.getSortDirection(),
limit: this.getPageLimit(),
offset: this.getPageOffset(),
pageIndex: this.getPageIndex(),
filters: this.getFilters()
};
},
function fetchDataIds() {
/* jshint validthis:true */
var dataIds = this.getDataInfoById();
if (dataIds !== undefined) {
return Promise.resolve(dataIds);
}
return coreUtils.promisify(_(this).getAllIds)
.then(function (ids) {
// initialize the selection to none selected
var itemSelectedByIdMap = {};
ids.forEach(function (idValue) {
itemSelectedByIdMap[idValue] = {selected: false};
});
setDataInfoById.call(this, itemSelectedByIdMap);
setSelectionLevel.call(this, SelectionLevels.NONE, true);
return itemSelectedByIdMap;
}.bind(this));
}
This is all the code. Logic to filter and sort is without any Promise.
Possible Solution
I removed setTimeout from mockGet function and it works without freezing UI. Currently everything is done at client side. And so I am not sure whether I will get same performance once I call REST API.
How do I resolve this issue of UI freeze with Promise.all ? Is there any workaround or is there any alternative ?

Deferred Promise - Resend same request if callback doesn't meet criteria

I am trying to achieve the following functionality:
execute call back
resolve promise
check output
if not correct execute again
I have 'mimicked' the scenario with a timer, this reruns a script that makes a call to backend database for some information:
_runCheckScript: function(bStart, bPreScript){
var oController = this;
var scriptTimerdeferred = $.Deferred();
var promise = scriptTimerdeferred.promise();
if(typeof(bStart) === "undefined"){
bStart = true;
}
if(typeof(bPreScript) === "undefined"){
bPreScript = true;
}
// if the HANA DB is not stopped or started, i.e. it is still starting up or shutting down
// check the status again every x number of seconds as per the function
var msTime = 10000;
if(!bPreScript){
this._pushTextIntoConsoleModel("output", {"text":"The instance will be 'pinged' every " + msTime/1000 + " seconds for 2 minutes to monitor for status changes. After this, the script will be terminated."});
}
if(bPreScript){
var timesRun = 0;
var commandTimer = setInterval( function () {
timesRun += 1;
if(timesRun === 12){
scriptTimerdeferred.reject();
clearInterval(commandTimer);
}
// send the deferred to the next function so it can be resolved when finished
oController._checkScript(scriptTimerdeferred, bStart, bPreScript);
}, msTime);
}
return $.Deferred(function() {
var dbcheckDeffered = this;
promise.done(function () {
dbcheckDeffered.resolve();
console.log('Check finished');
oController._pushTextIntoConsoleModel("output", {"text":"Check finished."});
});
});
The script it calls, has it's own promise as it calls another function:
_checkScript: function(scriptTimerdeferred, bStart, bPreScript){
var oProperties = this.getView().getModel("configModel");
var oParams = oProperties.getProperty("/oConfig/oParams");
var deferred = $.Deferred();
var promise = deferred.promise();
var sCompareStatus1 = "inProg";
var sCompareStatus2 = this._returnHanaCompareStatus(bStart, bPreScript);
var sCompareStatus3 = this._returnHanaCompareStatus3(bStart, bPreScript);
var params = {//some params};
// Send the command
this._sendAWSCommand(params, deferred);
// When command is sent
promise.done(function (oController) {
console.log('back to db check script');
var oCommandOutputModel = oController.getView().getModel("commandOutput");
var sStatus = oCommandOutputModel.Status;
// check that it's not in the wrong status for a start/stop
// or if it's a pre script check -> pre script checks always resolve first time
if(sStatus !== sCompareStatus1 && sStatus !== sCompareStatus2 && sStatus !==sCompareStatus3|| bPreScript){
scriptTimerdeferred.resolve();
}
});
},
This works, however what it does is:
set a timer to call the first script every x seconds (as the data is currently changing - a server is coming online)
the script runs and calls another function to get some data from the DB
when the call for data is resolved (complete) it comes back to 'promise.done' on the checkScript and only resolves the timer promise if it meets certain criteria
all the while, the initial timer is resending the call as eventually the DB will come online and the status will change
I am wondering if there is a better way to do this as currently I could have, for example, 3 calls to the DB that go unresolved then all resolve at the same time. I would prefer to run a command, wait for it to resolve, check the output, if it is not right then run command again.
Thanks!
I think what you want to do can be achieved carefully reading what explained in these links:
Promise Retry Design Patterns
In javascript, a function which returns promise and retries the inner async process best practice
See this jsfiddle
var max = 5;
var p = Promise.reject();
for(var i=0; i<max; i++) {
p = p.catch(attempt).then(test);
}
p = p.then(processResult).catch(errorHandler);
function attempt() {
var rand = Math.random();
if(rand < 0.8) {
throw rand;
} else {
return rand;
}
}
function test(val) {
if(val < 0.9) {
throw val;
} else {
return val;
}
}
function processResult(res) {
console.log(res);
}
function errorHandler(err) {
console.error(err);
}
It retries a promise infinite times since the condition is not satisfied. Your condition is the point you said "check the output". If your check fails, retry the promise. # Be careful to hold a limit case, promises waste memory. If your api/service/server/callreceiver is off, and you don't set a threshold, you could create an infinite chain of promises NO STOP

JavaScript result happening before callbacks complete

I'm totally new to JS having jumped in a few days ago to try make a chrome extension, so sorry if this is a simple problem, but I can't seem to figure it out.
My original function was to simply download an image and increment the stored count by 1 and add on the file size. However on a page of images it hit the write limits of chrome so I decided to count the values and write them at the end.
Initially the return value happened much later than when the function was executed (so it returned nothing), so I looked up how to fix it and got it working with a callback. However, although it waits for the callbacks, the code just continues past the callbacks and the part afterwards is executed before anything else, meaning the final count will always be 0.
// Loop through all urls
var approx_filesize = 0;
for(var i = 1; i < all_urls.length; i++){
var image_url = all_urls[i];
_download_image(image_url, folder_name, function(item){
approx_filesize += parseInt(item);
});
}
// This happens before any _download_image functions complete
alert('end' + approx_filesize);
// Write to storage
chrome.storage.sync.get({
download_count: 0,
download_size: 0
}, function(items) {
chrome.storage.sync.set({
download_count: parseInt(items.download_count) + all_images_data.length - 1,
download_size: parseInt(items.download_size) + approx_filesize
}, function() {
});
});
I just tried moving the loop into its own callback function and still had no luck, the alert runs before the first function completes.
function image_url_loop_callback(callback, folder_name, all_urls){
var approx_filesize = 0;
for(var i = 1; i < all_urls.length; i++){
var image_url = all_urls[i];
_download_image(image_url, folder_name, function(filesize){
approx_filesize += parseInt(filesize);
});
}
callback(approx_filesize);
}
image_url_loop_callback(function(approx_filesize){
alert(approx_filesize);
}, folder_name, all_urls);
How do I make it so that the loop completes before anything else is done?
Edit: Got it working with promise, here's the adjusted code:
new Promise( function(resolve, reject) {
var count = 1;
var num_items = all_urls.length;
var approx_filesize = 0;
for(var i = 1; i < num_items; i++){
var image_url = all_urls[i];
_download_image(image_url, folder_name, function(item){
approx_filesize += parseInt(item);
count ++;
if(count == num_items){
resolve([num_items, approx_filesize]);
}
});
}
}).then( function(stuff) {
var num_items = stuff[0];
var approx_filesize = stuff[1];
chrome.storage.sync.get({
download_count: 0,
download_size: 0
}, function(items) {
chrome.storage.sync.set({
download_count: parseInt(items.download_count) + num_items,
download_size: parseInt(items.download_size) + approx_filesize
}, function() {
});
});
});
Basically, you have to handle the asynchronous aspect of JavaScript.
To do so, you have to use a Promise.
This works this way:
new Promise( () => {
// My asynchronous code
}).then( () => {
// My code which need to wait for the promise resolution.
});
If you are working with only the latest versions of browsers, you can also have a look at async/await keywords which make asynchronous handling much easier than regular promises (but still are promises).
EDIT: As this answer required further explanation and proper code snippets, I edited it to answer a comment.
This example maybe easier to understand:
let myFoo = "Hello";
test = new Promise( (resolve) => {
console.log(myFoo);
myFoo = "World!";
setTimeout(() => {
resolve();
}, 4000);
}).then( () => {
console.log(myFoo);
});
This will print "Hello" immediately, and "World!" 4 seconds after.
This is how you work with promises. You can perfectly edit variables which are defined in a scope outside of the promise. Please don't use var, just stick to let and define a decent scope.
Due to javascript's async nature you have to use promises:
https://developers.google.com/web/fundamentals/getting-started/primers/promises

javascript/jquery: Iterative called function; wait till the previous call is finished

I've some problem with a library calling a function on each item. I've to check the state for this item via an ajax request and don't want to call one request per item, but get a range of item states.
Because these items are dates I can get some range pretty easy - that's the good part :)
So to to give some code ...
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
return checkState(item);
}
}
function checkState(item) {
if(!itemStates.hasOwnProperty(item)) {
$.get('...', function(result) {
$.extend(true, itemStates, result);
});
}
return itemStates[item];
}
The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremly high the next items' state was allready requested within the previous request.
I read about the defer and wait(), then() and so on, but couldn't really get an idea how to implement this.
Many thanks to everybody who could help me with this :)
You can achieve this by using jQuery.Deferred or Javascript Promise. In the following code, itemCallback() will wait for previous calls to finish before calling checkState().
var queue = [];
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
var def = $.Deferred();
$.when.apply(null, queue)
.then(function() {
return checkState(item);
})
.then(function(result) {
def.resolve(result);
});
queue.push(def.promise());
return def.promise();
}
}
function checkState(item) {
var def = $.Deferred();
if (!itemStates.hasOwnProperty(item)) {
$.get('...', function(result) {
$.extend(true, itemStates, result);
def.resolve(itemStates[item]);
});
} else
def.resolve(itemStates[item]);
return def.promise();
}
//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).done(function(r) { console.log(r); });
libraryObj.itemCallback(2).done(function(r) { console.log(r); });
libraryObj.itemCallback(3).done(function(r) { console.log(r); });
libraryObj.itemCallback(4).done(function(r) { console.log(r); });
libraryObj.itemCallback(5).done(function(r) { console.log(r); });
Same example built with Javascript Promises
var queue = [];
var itemStates = {};
var libraryObj = {
itemCallback: function(item) {
var promise = new Promise(resolve => {
Promise.all(queue)
.then(() => checkState(item))
.then((result) => resolve(result));
});
queue.push(promise);
return promise;
}
}
function checkState(item) {
return new Promise(resolve => {
if (item in itemStates)
resolve(itemStates[item]);
else {
$.get('...', function(result) {
$.extend(true, itemStates, result);
resolve(itemStates[item]);
});
}
});
}
//these will execute in order, waiting for the previous call
libraryObj.itemCallback(1).then(function(r) { console.log(r); });
libraryObj.itemCallback(2).then(function(r) { console.log(r); });
libraryObj.itemCallback(3).then(function(r) { console.log(r); });
libraryObj.itemCallback(4).then(function(r) { console.log(r); });
libraryObj.itemCallback(5).then(function(r) { console.log(r); });
The library is now calling library.itemCallback() on each item, but I want to wait for the request made in checkState() before calling checkState() again (because the chance is extremely high the next items' state was already requested within the previous request.
One thing I can think of doing is making some caching function, depending on the last time the function was called return the previous value or make a new request
var cached = function(self, cachingTime, fn){
var paramMap = {};
return function( ) {
var arr = Array.prototype.slice.call(arguments);
var parameters = JSON.stringify(arr);
var returning;
if(!paramMap[parameters]){
returning = fn.apply(self,arr);
paramMap[parameters]={timeCalled: new Date(), value:returning};
} else {
var diffMs = Math.abs(paramMap[parameters].timeCalled - new Date());
var diffMins = ( diffMs / 1000 ) / 60;
if(diffMins > cachingTime){
returning = fn.apply(self,arr);
paramMap[parameters] = {timeCalled: new Date(), value:returning};
} else {
returning = paramMap[parameters].value;
}
}
return returning;
}
}
Then you'd wrap the ajax call into the function you've made
var fn = cached(null, 1 , function(item){
return $.get('...', function(result) {
$.extend(true, itemStates, result);
});
});
Executing the new function would get you the last promise called for those parameters within the last request made at the last minute with those parameters or make a new request
simplest and dirty way of taking control over the library is to override their methods
But I don't really know core problem here so other hints are below
If you have the control over the checkState then just collect your data and change your controller on the server side to work with arrays that's it
and if you don't know when the next checkState will be called to count your collection and make the request use setTimeout to check collection after some time or setIterval to check it continuously
if you don't want to get same item multiple times then store your checked items in some variable like alreadyChecked and before making request search for this item in alreadyChecked
to be notified when some library is using your item use getter,
and then collect your items.
When you will have enough items collected then you can make the request,
but when you will not have enought items then use setTimeout and wait for some time. If nothing changes, then it means that library finishes the iteration for now and you can make the request with items that left of.
let collection=[];// collection for request
let _items={};// real items for you when you don't want to perfrom actions while getting values
let itemStates={};// items for library
let timeoutId;
//instead of itemStates[someState]=someValue; use
function setItem(someState,someValue){
Object.defineProperty(itemStates, someState, { get: function () {
if(typeof timeoutId=="number")clearTimeout(timeoutId);
//here you can add someState to the collection for request
collection.push(_items[someState]);
if(collection.length>=10){
makeRequest();
}else{
timeoutId=setTimeout(()=>{...checkCollectionAndMakeRequest...},someTime);
}
return someValue;
} });
}

Managing a queue in JavaScript via callbacks

I am working on a page that uses JavaScript to manage a queue. My challenge is my code has nested callbacks. The nested callbacks are confusing me in regards to the scope of my queue. Currently, I have the following:
function MyApp() {}
module.exports = MyApp;
MyApp.myQueue = [];
MyApp.queueIsLocked = false;
MyApp.enqueue = function(item, onSuccess, onFailure) {
if (!MyApp.queueIsLocked) {
MyApp.queueIsLocked = true;
MyApp.myQueue.push(item);
MyApp.queueIsLocked = false;
item.send(
function() {
console.log('item: ' + item.id);
MyApp.queueIsLocked = true;
MyApp.findItemById(item.id,
function(index) {
if (index !== -1) {
MyApp.myQueue.splice(index, 1);
MyApp.queueIsLocked = false;
if (onSuccess) {
onSuccess(item.id);
}
}
}
);
},
function() {
alert('Unable to send item to the server.');
if (onFailure) {
onFailure();
}
}
);
}
};
MyApp.findItemById = function(id, onComplete) {
var index = -1;
if (MyApp.queueIsLocked) {
setTimeout(function() {
// Attempt to find the index again.
}, 100);
} else {
MyApp.queueIsLocked = true;
for (var i=0; i<MyApp.myQueue.length; i++) {
if (MyApp.myQueue[i].id === id) {
index = i;
break;
}
}
}
if (onComplete) {
onComplete(index);
}
};
The send function behaves differently based on the details of item. Sometimes the item will be sent to one server. Sometimes, it will be sent to multiple servers. Either way, I do not know when the item will be done being "sent". For that reason, I'm using a callback to manage the queue. When the item is done being "sent", I want to remove it from the queue. I need to use either a timeout or interval to check to see if the queue is locked or not. If its not locked, I want to remove the item from the queue. This check is adding another level of nesting that is confusing me.
My challenge is, I do not believe that the scope of index is working like I expected. I feel like I'm getting a race condition. I'm basing this on the fact that I've written the following Jasmine test:
describe('Queue', function() {
describe('Approach 1', function() {
it('should do something', function() {
MyApp.enqueue({id:'QRA', text:'Test A'});
});
});
describe('Approach 2', function() {
it('should successfully queue and dequeue items', function() {
MyApp.enqueue({id:'WX1', text:'Test 1'});
MyApp.enqueue({id:'QV2', text:'Test 2'});
MyApp.enqueue({id:'ZE3', text:'Test 3'});
});
});
});
When I execute the test, I see the following in the console window:
item: QRA index: 1
item: WX1 index: 2
item: QV2 index: 3
item: ZE3 index: 4
Its like the items aren't getting dequeued like I would expect. Am I way off base in my approach of managing a queue? What am I doing wrong?
Thank you for any assistance.
Here are some questions you need to think through and answer for yourself about your intent and design:
It sounds like the queue represents items you are trying to send to the server. You are adding items to the queue that need to be sent, and removing them from the queue after they have been successfully sent.
Do you want your code to send multiple items simultaneously, in parallel? For example, item A is added to the queue, then sent. Before the asynchronous send for A finishes, item B is added to the list. Should the code try to send item B before the send of item A finishes? Based on your code, it sounds like yes.
It seems that you don't really want/need a queue, per se, so much as you want a list to track which items are in the process of being sent. "Queue" implies that objects are being processed in some kind of FIFO order.
If you just want to track items based on id, then you can use an object instead. For example:
MyApp.items = {};
MyApp.addItem = function(item){
MyApp.items[item.id] = item;
item.send(
function(){ // success
MyApp.removeItem(item.id)
}
);
}
MyApp.removeItem = function(id){
delete MyApp.items[id];
onSuccess(id);
}
Also, I don't think you need a lock on the queue. Javascript is single-threaded, so you'll never have a case where two parts of your code are trying to operate on the queue at the same time. When an ajax call finishes asynchronously, your callback code won't actually be executed until any other code currently executing finishes.
The big flaw I'm seeing is that you call MyApp.queueIsLocked = true immediately before MyApp.findItemById. Because it's locked, the function sets up a timeout (that does nothing), and proceeds to call onComplete(-1). -1 is then explicitly ignored by onComplete, failing to dequeue, and locking your queue.
You probably meant to retry the find, like this:
setTimeout(function() {
// Attempt to find the index again.
MyApp.findItemById(id, onComplete);
}, 100);
I'm not sure, but I think Jasmine requires explicit instruction to get Timeout functions to fire, using jasmine.clock().tick
That said, I suggest removing all of the references to queueIsLocked, including the above timeout code. Also, if item.id is always a unique string, you can use an object instead of an array to store your values.
Here is a suggested iteration, staying as true to the original API as possible:
function MyApp() {}
module.exports = MyApp;
MyApp.myQueue = {};
//Sends the item, calling onSuccess or onFailure when finished
// item will appear in MyApp.myQueue while waiting for a response from send
MyApp.enqueue = function(item, onSuccess, onFailure) {
MyApp.myQueue[item.id] = item;
item.send(function() {
console.log('item: ' + item.id);
delete MyApp.myQueue[item.id];
if (onSuccess) {
onSuccess(item.id);
}
}, function() {
alert('Unable to send item to the server.');
if (onFailure) {
onFailure();
}
});
};
//Returns the Item in the queue, or undefined if not found
MyApp.findItemById = function(id, onComplete) {
if (onComplete) {
onComplete(id);
}
return MyApp.myQueue[id];
};
Try to use ECMA 6 Promise or any promise from js framework.Promiseis more suitable for this task. see more at https://developer.mozilla.org/
function MyApp() {}
module.exports = MyApp;
MyApp.myQueue = [];
MyApp.queueIsLocked = false;
MyApp.enqueue = function(item) {
return new Promise(function(resolve, reject) {
if (!MyApp.queueIsLocked) {
MyApp.queueIsLocked = true;
MyApp.myQueue.push(item);
MyApp.queueIsLocked = false;
var onResolve = function() {
console.log('item: ' + item.id);
MyApp.queueIsLocked = true;
MyApp.findItemById(item.id).then(function(index){
if (index !== -1) {
MyApp.myQueue.splice(index, 1);
MyApp.queueIsLocked = false;
resolve(item.id);
}
});
};
item.send(onResolve,reject);
}
});
};
MyApp.findItemById = function(id) {
return new Promise(function(resolve, reject) {
var index = -1;
if (MyApp.queueIsLocked) {
setTimeout(function() {
// Attempt to find the index again.
}, 100);
} else {
MyApp.queueIsLocked = true;
for (var i=0; i<MyApp.myQueue.length; i++) {
if (MyApp.myQueue[i].id === id) {
index = i;
break;
}
}
resolve(index);
}
});
};
move MyApp.queueIsLocked = false; to the callback of server send

Categories

Resources