I have to search through word index tables with potentially hundreds of thousands rows. I can restrict my search by passing a list of documents to the search. Request to search for words in many documents return very slowly. So...to improve the UX we're chunking the request into several groups of documents. So, if a user asks to search 90 documents, and the chunk size is 10 documents per query, then we send out 90 / 10 = 9 independent $.ajax() calls. We want the results to come in the order they were sent.
We implement this recursion:
var SearchFunction = function () {
$.ajax(/* ... */);
}
var RecursiveSearch = function () {
var deferred = $.Deferred();
if (arrTransSearch.length > 0) {
deferred = SearchDocuments(arrTransSearch.shift());
}
else {
deferred.reject();
}
return deferred.promise().then(RecursiveSearch);
}
if (arrTransSearch.length > 1) {
RecursiveSearch().fail(SomeFunction);
}
var SomeFunction = function () {
alert("Failed. Yes!");
}
When I debug the code, it appears that deferred.reject() does not change the state of deferred.promise(). That is, when the next line
return deferred.promise().then(RecursiveSearch)
is executed, it just loops back into the recursive function, instead of exiting the recursion and falling into
RecursiveSearch().fail(SomeFunction);
Important Note:
I'm using jQuery-1.7.1. I ran analogous recursion in JSFiddle (Thank you Beeetroot-Beetroot) and it failed on jQuery-1.7.2 while on jQuery-2.1.0 it ran without a problem.
Any idea on how to get the recursion to work in jQuery-1.7.1?
A pattern partly covering what you are looking for is provided here under the heading "The Collection Kerfuffle". You actually need slightly more than that because you want to address your list of document referneces in chunks (groups).
The code will be something like this :
$(function() {
//General ajax options for searching a document group
var ajaxOptions = {
url: '...',
type: 'POST',
//data: ... //added dynamically
dataType: 'JSON',
// etc.
};
//
function searchDocumentsInGroups(arr, n) {
//Pre-process arr to create an array of arrays, where each inner array is a group of document references
var groups = [];
$.each(arr, function (i) {
if (!(i % n)) groups.push(arr.slice(i, i + n));
});
//Ajax serializer (from the Collection Kerfuffle reference)
return groups.reduce(function (promise, group) {
return promise.then(function () {
return $.ajax($.extend({}, ajaxOptions, {
data: JSON.stringify(group);//or whatever, compatible with the server-side script
})).then(function (groupResults) {
//display groupResults here
});
});
}, $.when(0));
}
// data
var myDocumentArray = [ 'doc1', 'doc2', 'doc3', 'doc4', 'etc.' ], //Your array of 90 document references.
groupSize = 10; //Number of documents per "chunk".
// Event handler to kick off the process.
$("#searchDocuments").on('click', function () {
// display "in progress" message or spinner here
searchDocumentsInGroups(myDocumentArray, groupSize).then(function () {
// display "complete" message or hide spinner here
});
});
});
You also need the Polyfill for Array.prototype.reduce, as .reduce is relied on above and older browsers (pre ECMAScript5) don't have it.
if ( 'function' !== typeof Array.prototype.reduce ) {
Array.prototype.reduce = function( callback /*, initialValue*/ ) {
'use strict';
if ( null === this || 'undefined' === typeof this ) {
throw new TypeError(
'Array.prototype.reduce called on null or undefined' );
}
if ( 'function' !== typeof callback ) {
throw new TypeError( callback + ' is not a function' );
}
var t = Object( this ), len = t.length >>> 0, k = 0, value;
if ( arguments.length >= 2 ) {
value = arguments[1];
} else {
while ( k < len && ! k in t ) k++;
if ( k >= len )
throw new TypeError('Reduce of empty array with no initial value');
value = t[ k++ ];
}
for ( ; k < len ; k++ ) {
if ( k in t ) {
value = callback( value, t[k], k, t );
}
}
return value;
};
}
All untested but I recently answered a similar question here, with a link to a fiddle.
It turns out that until jQuery-1.8, calling $.then() with one argument was the equivalent of calling $.then(successFunction, successFunction). Since I was using jQuery-1.7.1, a rejected promise would still invoke the recursion.
Related
I'm having a problem understanding a line of code from Eloquent Javascript ebook, Chapter 11 (Message Routing section). In it the author tries to explain how message routing in a supposed network might work (by incorporating promises and other async concepts). He constructs different types of functions that handle different actions (sending request, receiving it, responding,...). But then there is this implementation of route finding algorithm that I think I don't quite understand.
//SECTION THAT CREATES A KIND OF NEIGHBOUR MAP THAT EVERY NEST (COMPUTER) HAS
requestType("connections", (nest, {name, neighbors},
source) => {
let connections = nest.state.connections;
if (JSON.stringify(connections.get(name)) ==
JSON.stringify(neighbors)) return;
connections.set(name, neighbors);
broadcastConnections(nest, name, source);
});
function broadcastConnections(nest, name, exceptFor = null) {
for (let neighbor of nest.neighbors) {
if (neighbor == exceptFor) continue;
request(nest, neighbor, "connections", {
name,
neighbors: nest.state.connections.get(name)
});
}
}
everywhere(nest => {
nest.state.connections = new Map();
nest.state.connections.set(nest.name, nest.neighbors);
broadcastConnections(nest, nest.name);
});
//PATH FINDING FUNCTION
function findRoute(from, to, connections) {
let work = [{at: from, via: null}];
for (let i = 0; i < work.length; i++) {
let {at, via} = work[i];
for (let next of connections.get(at) || []) {
if (next == to) return via;
if (!work.some(w => w.at == next)) {
work.push({at: next, via: via || next});
}
}
}
return null;
}
//THEN THERE ARE FUNCTIONS THAT HANDLE THE ACTUAL MESSAGE SENDING/ROUTING
function routeRequest(nest, target, type, content) {
if (nest.neighbors.includes(target)) {
return request(nest, target, type, content);
} else {
let via = findRoute(nest.name, target,
nest.state.connections);
if (!via) throw new Error(`No route to ${target}`);
return request(nest, via, "route",
{target, type, content});
}
}
requestType("route", (nest, {target, type, content}) => {
return routeRequest(nest, target, type, content);
});
My question is, in the findRoute function, why is there || [] in the inner for loop? Is it there for appropriate consequent error handling (in case somehow there is no nest specified as having neighbours in the connections property, but is regardless of that listed as someones neighbouring nest)?
connections.get(at) may return null or undefined, depending on the api, and you can't do a for...of loop over null or undefined, so he replaces that value with an empty array in that case
I'm new to the Q library and I have understanding problems with the Q's promises for array methods.
I have a array with various key strings to be replaced by a delayed function.
After the passed in array got successfully replaced, I need to go on with the now fulfilled array.
How do I wait to for the array to be replaced in this case?
Output looks like
key: "p",
text: "Good morning %text%",
inside: key: "p"
text: "Good afternoon user"
inside: key: "p"
text: "This user has to be replaced."
As you can see not all keys got replaced.
Code sample
var object = [];
object.key = "p";
object.text = "Good morning %text%";
object.inside = [];
object.inside.key = "p";
object.inside.text = "Good afternoon %text%";
object.inside.inside = [];
object.inside.inside.key = "p";
object.inside.inside.text = "This %text% has to be replaced.";
goDeep(object, 0);
console.log(object);
function goDeep(data) {
if (data instanceof Object) {
Object.keys(data).forEach(function(key) {
if (data[key] instanceof Object) {
goDeep(data[key]);
} else if (data[key].inside) {
goDeep(data[key].inside);
} else {
var match = scanText(data[key]);
if (match && !(match instanceof Boolean)) {
getServerData(match.input).then (function(response) {
var splitData = data[key].match(/%.*%/)[0].split(",");
for (ii = 0; ii < splitData.length; ++ii) {
splitData[ii] = splitData[ii].trim();
var replaceData = data[key].replace(splitData[ii], response);
// Data gets replaced here
data[key] = replaceData;
};
});
}
}
});
}
}
function scanText(data) {
var match = data.match("(%.*%)", "/g");
if (match) {
return match;
} else {
return false;
}
}
function getServerData(data) {
return Q.delay(1000).thenResolve("user");
}
First let me correct slightly what do you really want,
Calling an asynchronous function recursively using JavaScript promise
Note: I am note aware of the Q Library, but I am sure it will be similar to others promise implementation
I will try my best to simplify your problem so I will explain it in steps
1. How to turn an array of promises to a single promise
you need the all method
Q.all([
getWeather({name:'beirut'}),
getWeather({name:'paris'}),
getWeather({name:'madrid'})
]).then(function(beirut, paris, madrid){
// here I am sure that the 3 methods where completed
});
2. How to convert an array of parameters to an array of promises
using array.map
['beirut', 'paris', 'madrid'].map(function(city){ return getWeather(city) });
3. Implement a recursive mechanism
function getWeather(area) {
// this function should return a promise of the weather response
var deferred = Q.defer();
setTimeout(function () {
deferred.resolve({
name: 'beirut',
children: [{
name: 'sub area'
}, ...]
});
}, 300);
return deffered.promise;
}
function resolveChildren(parentArea, level) {
var deferred = Q.defer();
getWeather(parentArea).then(function (children) {
var promise = Q.all(children.map(function ( /* a sub area */ child) {
// if this a leaf then resolve the child, else go deeper and get the children of this child
// suppose child.isleaf return true if this is a leaf node
if (child.isleaf) {
return child;
}
return resolveChildren(child, level + 1);
}));
promise.then(function (children) {
parentArea.children = children;
deferred.resolve(parentArea);
});
}, function (err) {
// failed to get children for some reason
parentArea.children = null;
deferred.resolve(parentArea);
});
return deffered.promise;
}
I have coded a javascript file:
$(function() {
return $(".ajax-form").on("ajax:success", function(e, data, status, xhr) {
var model_name;
model_name = $(this).data('model-name');
console.log('ajax form success');
if (model_name === 'contact') {
return $('#modal-alert-contact').modal('show');
} else {
return $('#modal-alert-demo').modal('show');
}
}).bind("ajax:error", function(e, xhr, status, error) {
var elm, messages, model_name;
model_name = $(this).data('model-name');
console.log('ajax form error');
console.log(model_name);
if (model_name === 'contact') {
if (xhr.responseJSON["email"]) {
elm = $('.alert-contact-fields');
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
} else {
elm = $('.alert-contact-fields');
return elm.addClass('hide');
}
} else {
if (xhr.responseJSON["company_name"]) {
elm = $('.alert-demo-fields');
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
} else {
elm = $('.alert-demo-fields');
return elm.addClass('hide');
}
}
});
});
and I found it out messy, and repeating same codes.
What I'm want to do is this part:
messages = [];
$.each(xhr.responseJSON, function(id, error_messages) {
return messages.push(("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " "));
});
elm.find('.messages').html(messages);
return elm.removeClass('hide');
I want that part to be a function, and after I do that, I will call that function to use it on my function. Is it possible or there's some technique to improve my coding structure?
Thanks!
I think you want something like this:
$(function() {
var myform = $(".ajax-form");
var makeMessages = function(json) {
return $.map(json, function(error_messages, id) {
return ("<li><strong class='titleize'>" + id + "</strong> - can't be blank</li>").replace(/_/g, " ");
});
};
myform.on('ajax:success', function(e, data, status, xhr) {
var modal_to_show = ($(this).data('model-name') === 'contact') ? '#modal-alert-contact' : '#modal-alet-demo';
return $(modal_to_show).modal('show');
});
myform.on('ajax:error', function(e, xhr, status, error) {
var fields;
if ($(this).data('model-name') === 'contact') {
fields = $('.alert-contact-fields');
if (xhr.responseJSON["email"]) {
return fields.find('messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
}
fields = $('.alert-demo-fields');
if (xhr.responseJSON["company_name"]) {
return fields.find('.messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
});
});
makeMessages is a function that takes the json object and returns a set of strings; map() is a much better function for that than each(), because it requires no intermediate array to save the values.
The 'success' handler shows the use of the 'ternary operator', also known as a conditional expression. You want to know which modal to show: this is how you pick it, then have one and only one 'show' operation. It's way easier to debug.
For the 'error' handler, each time you set the messages, you just call makeMessages() with your JSON and get back the array of strings you want. Because you had to find the messages field inside the alert-*-fields, I call end() which pops the current jquery context back one search (from the 'find' to the initial $() call) and then call 'show' on it instead.
Since you call 'return' at the end of a chosen successful operation, there is no need at all for 'else' statements. They're noise. Either your code does its thing, or it falls through to the next stage.
You could remove my fields = set operations, since performance-wise each will only be called once, so it's harmless to have that repetition. But this makes explicit which region you're working in.
If you want to get insanely dense about what you're working on, put all the decision-making up top (what to work on, what to show), and make the rest of the code pure machinery, the 'how' part of your code. The 'error' handler becomes this:
myform.on('ajax:error', function(e, xhr, status, error) {
var handler = (($(this).data('model-name') === 'contact') ?
{ 'fieldclass': '.alert-contact-fields', 'objname': 'email' } :
{ 'fieldclass': '.alert-demo-fields', 'objname': 'company_name' });
var fields = $(handler.fieldclass);
if (xhr.responseJSON[handler.objname]) {
return fields.find('.messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
});
At which point 'makeMessages()' just becomes a nice, convenient function because it shows (and names! Good names are always important to maintenance) what you're doing with the JSON object.
One (well, two) last alternatives:
myform.on('ajax:error', function(e, xhr, status, error) {
var drawResults = function(fieldclass, objname) {
var fields = $(fieldclass);
if (xhr.responseJSON[objname]) {
return fields.find('messages').html(makeMessages(xhr.responseJSON)).end().removeClass('hide');
}
return fields.addClass('hide');
};
return ($(this).data('model-name') === 'contact' ?
drawResults('.alert-contact-fields', 'email') :
drawResults('.alert-data-fields', 'company_name'));
/* Absolutely minimal alternative, but some people find
* using the apply method obfuscating. */
return drawResults.apply(this, $(this).data('model-name') === 'contact' ?
['.alert-contact-fields', 'email'] :
['.alert-data-fields', 'company_name']);
});
Rather than use fields and decisions up front, this puts all the decision making at the end, and describes what will happen once the decision is made up front. This uses a more familiar syntax of calling a function. It's important to see that drawResults() has access to the xhr object already, so it isn't necessary to pass it in.
One last possible extraction is to turn $(this).data('model-name') === 'contact' into a function, like isDemo(), so that code only happens once and is also well-named.
I have a problem with js and timing, which i don't understand
project.pro.register_product = function(callback) {
project.pro.selected.items.push(res.product);
if( typeof callback === 'function' ) {
callback();
project.pro.update_allprice();
project.audio.play_success();
};
}
project.pro.update_allprice = function() {
var a_price = 0.0;
$.each( project.pro.selected.items, function(i,v) {
a_price += parseFloat(v['price']); // here i get sometimes a not found error
})
}
On the last but one line some times i get an error. I thoght, that push runs synchron. What can be my problem?
Any ideas?
Below you see a part of the server-side code of the twich.me node.js chat:
exports.channel = function(MESSAGE_BACKLOG, MESSAGE_TRUNCATE) {
return (function() {
var messages = [],
callbacks = [];
return {
appendMessage : function (nick, room, type, text) {
//truncate message if necessary
if (type == 'msg' && text.length > MESSAGE_TRUNCATE) {
text = text.substr(0, MESSAGE_TRUNCATE) + "... (trunc.)";
}
//message
var m = {
nick: nick,
type: type, // "msg", "join", "part"
text: text,
room: room,
timestamp: (new Date()).getTime()
};
//output to console
// mlog(m);
//push msg on message stack
messages.push( m );
//???
while (callbacks.length > 0) {
callbacks.shift().callback([m]);
}
//old messages get pushed out of message stack
while (messages.length > MESSAGE_BACKLOG) {
messages.shift();
}
},
query : function (room, since, callback) {
var matching = [];
for (var i = 0; i < messages.length; i++) {
var message = messages[i];
if (message.timestamp > since && room == message.room) {
matching.push(message)
}
}
//???
if (matching.length != 0) {
callback(matching);
}
else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
},
//run initially when script starts
init : function() {
// clear old callbacks older than 25 seconds (lowered from 30 seconds to get round rmit proxy server's 30sec timeout
setInterval(function () {
var now = new Date();
while (callbacks.length > 0 && now - callbacks[0].timestamp > 25*1000) {
callbacks.shift().callback([]);
}
}, 3000);
return "hi";
}
}
}());
}
The code is responsible for storing and retrieving chat messages from one of the chat rooms.
I am not a javascript programmer. My background is with PHP where everything is procedural. I want to solve this with memcached instead. But first I need to understand what exactly is going on. I have added extra comments. What I don't understand is all the stuff with the callbacks. Could you help me understand what the callbacks are doing?
I don't really understand what you want but here's what going on:
while (callbacks.length > 0) {
callbacks.shift().callback([m]);
}
while the amount of objects in the array callbacks is greater than 0,
callbacks.shift() function will apparently return an object with a property called callback which is a function. and it's calling that function with an array that has the variable m in it.
if (matching.length != 0) {
callback(matching);
}
else {
callbacks.push({ timestamp: new Date(), callback: callback });
}
}
if the amount of objects in the array matching is not 0, call the function callback or if it does, cal the function callback.push with an object.