I've got some examples that might help here: <a href="https://wiki.roll20.net/API:Cookbook#Underscore.j" rel="nofollow">https://wiki.roll20.net/API:Cookbook#Underscore.j</a>... Let's take a fairly simple script: on('ready',() => { on('chat:message',(msg) => { if('api' === msg.type && /!show-rep\b/i.test(msg.content) ){ let who = getObj('player',msg.playerid).get('displayname');
if(msg.selected){ _.chain(msg.selected) .map( o => getObj('graphic',o._id)) .reject(_.isUndefined) .filter( t => t.get('represents').length ) .map( t =>({token:t,character:getObj('character',t.get('represents'))})) .reject( o =>_.isUndefined(o.character)) .map( o => `<div><div><img style="max-width: 3em; max-height: 3em; border: 1px solid #999; background-color: white;" src="${o.token.get('imgsrc')}"></div><div><b>${o.character.get('name')}</b></div></div>`) .tap(out => { sendChat('',`/w "${who}" ${out.join('')}`); }); } else { sendChat('',`/w "${who}" <b>No tokens selected</b>`); } } }); }); All this does is print the icon and name for each selected token that represents a character: !show-rep Here is the code that does the work: _.chain(msg.selected) .map( o => getObj('graphic',o._id)) .reject(_.isUndefined) .filter( t => t.get('represents').length ) .map( t =>({token:t,character:getObj('character',t.get('represents'))})) .reject( o =>_.isUndefined(o.character)) .map( o => `<div><div><img style="max-width: 3em; max-height: 3em; border: 1px solid #999; background-color: white;" src="${o.token.get('imgsrc')}"></div><div><b>${o.character.get('name')}</b></div></div>`) .tap(out => { sendChat('',`/w "${who}" ${out.join('')}`); }); This could be rewritten in a traditional form like this: on('ready',() => { on('chat:message',(msg) => { if('api' === msg.type && /!show-rep\b/i.test(msg.content) ){ let who = getObj('player',msg.playerid).get('displayname');
if(msg.selected){ let data = []; msg.selected.forEach( s => { let t = getObj('graphic', s._id); if(t && t.get('represents').length ){ let c = getObj('character', t.get('represents')); data.push( `<div><div><img style="max-width: 3em; max-height: 3em; border: 1px solid #999; background-color: white;" src="${t.get('imgsrc')}"></div><div><b>${c.get('name')}</b></div></div>` ); } }); sendChat('',`/w "${who}" ${data.join('')}`); } else { sendChat('',`/w "${who}" <b>No tokens selected</b>`); } } }); }); This has identical output. It's about the same length, so why would you ever use that chaining method? Clarity and agility. Once you understand the chaining pattern, it's really easy to see what it's doing and modify it to do other things. The first 6 lines of that chain() show up over and over again in scripts I write. Lets go over the whole thing line by line. The idea of chain() is that the output of each step is the input to the next step. The argument to chain() becomes the first state (here state just means what's being passed between the functions, not the global state object in the API): _.chain(msg.selected) Here I've passed the msg.selected, which means the starting state will look something like: [ {_type: 'graphic', _id: 'abc'}, {_type: 'graphic', _id: 'cda'}, ... ] Next I take this state and turn it into an array of objects using .map() . Normally, _.map() 's first argument would be the collection to map over. In a chain, the first argument is fulfilled by the chain's current state instead and the argument becomes the function to apply (all the arguments shift to the left). The o passed to the function will be each of the objects in the above msg.selected. I could have passed o._type as the first argument to getObj() , but since I'm only interested in graphics (there might be drawings, text, etc), forcing it to 'graphic' means I'll get undefined for some entries, the ones that aren't token s. The first iteration o._id will be 'abc' , the second it will be 'cda' , etc. .map( o => getObj('graphic',o._id)) Now the state will look something like: [ {Roll20 Graphic Object}, {Roll20 Graphic Object}, undefined, {Roll20 Graphic Object}, ...] Next I want to get rid of those not graphic objects, so I can .reject() them by passing a function that will match the ones I don't want. In this case, I don't want the undefined objects, so passing _.isUndefined will cause _.isUndefined() to be called on each entry, similar to .map() , except anything that the function is true for will be omitted from the result. ( .filter() is the opposite function, which will omit anything that the function returns false for.) Note that I am passing the _.isUndefined Function Object, not an _.isUndefined() invocation of the function. .reject(_.isUndefined) Now the state will look like this: [ {Roll20 Graphic Object}, {Roll20 Graphic Object}, {Roll20 Graphic Object}, ...] Next I want to only keep the tokens that represent a character. I do this with .filter() and pass it a function that returns the length of the represents id for each graphic. The default is '' , which has a length of 0 , so only tokens that represent something will be retained. .filter( t => t.get('represents').length ) Now I might be down to only a few results: [ {Roll20 Graphic Object}, {Roll20 Graphic Object}] Next I want to change my representation to contain the character and the token, so I map across my state and return a new object with the token and the character: .map( t =>({token:t,character:getObj('character',t.get('represents'))})) Now my state could look like this: [ { token: {Roll20 Graphic Object} character: {Roll20 Character Object}},{ token: {Roll20 Graphic Object} character: undefined }] Next I get rid of any undefined characters (technically, this shouldn't happen since I checked for only tokens that represent, but in the odd case that a token represents a character that doesn't exist, I want to ignore them.). This is just like above, but since my undefined is in a property, I pass a function that checks that property: .reject( o =>_.isUndefined(o.character)) Now my state would be something like: [ { token: {Roll20 Graphic Object} character: {Roll20 Character Object}}] Next I want to change my representation again to be just the output for each token/character entry. I use map to take in the {token:...,character:...} object and return a string: .map( o => `<div><div><img style="max-width: 3em; max-height: 3em; border: 1px solid #999; background-color: white;" src="${o.token.get('imgsrc')}"></div><div><b>${o.character.get('name')}</b></div></div>`) Now my state will be something like: ["<div><div><img ... src="..."></div><div><b>ABC Character</b></div></div>"] and lastly, I take this array into .tap() in whole, join it together and output it as a whisper. .tap(out => { sendChat('',`/w "${who}" ${out.join('')}`); }); Hopefully that helps you understand what's going on with the chaining stuff. Jakob will point out (if I don't mention it first) that there are more idiomatic ways of doing this in modern Javascript. =D I still think it's worthwhile to learn this pattern as it is somewhat clearer in many cases, and since it's the basis for TAS, will make understanding TAS easier. =D