The basic technique is to create work queues and then process them in small chunks asynchronously. Your countCharacterClasses would look something like this: (untested!) countCharacterClasses = function(msg, namearray){
state.CharacterCount.active = true;
sendChat('Character Count', `/w ${msg.who}<br> Checking Classes now!`, null, {noarchive:true});
// build classes count object: { ranger: 0, monk: 0, ... }
let classes = _.object( namearray, _.times( namearray.length, _.constant(0) ) ),
// setup work queue: [ {id: -kJas123123af, name: 'ranger'}, ... ]
work = _.chain(characterlist)
.pluck('id') //< gets id from characterlist
.reduce((m, id)=>{ //< builds the {id: ..., name: ...} objects
_.each(namearray,(n)=>{
m.push({id:id,name:n});
});
return m;
},[])
.value(), //< returns the array of objects
// create a function that handles a single instance
// of {id: ..., name: ...} or reports if there aren't any more.
workQueue = ()=>{
// checks if there are things left to work on
if(work.length){
// removes the next thing in the list
let part = work.shift();
// adds the levels of the class or 0
classes[part.name]+=parseInt(getAttrByName(part.id,`${part.name}_level`),10)||0;
// defers calling itself for the next bit of work.
// could use _.delay(workQueue,10) or similar
_.defer(workQueue);
} else {
// if there is nothing left in the work queue, output the data.
printOutput(classes,'Combined Class levels', msg);
}
};
// kick of the first iteration of workQueue
workQueue();
},
This will be Asynchronous, so wherever you're calling countCharacterClasses(), it will return immediately. Just as an aside, anywhere you can use findObjs() followed by _.filter() instead of filterObjs(), it will be much faster because of the new indexing.