Jimbo Jones
263 6th St., Suite 206
Springfield, NT 01646
Hours: 8 AM - 5 PM
Tel: 800-267-8634, ext 82
Seymore Skinner
263 6th St., Suite 206
Springfield, NT 01646
Hours: 11 AM - 7 PM
Tel: 800-267-8634, ext 59
Carl Carlton
2354 N. President St.
Springfield, NT 01649
Hours: 9 AM - 6 PM
Tel: 877-236-0224
For every visitor to our site, display a list of five sales agents. Only show agents with open office hours. Of those, show the ones that are closest to the visitor's location first. For sales agents that share an office, list them in random order so they get equal chances for commissions.
Create an empty Array A.
Loop through the reps.
If a rep is not currently available, go to the next one.
Calculate the distance between the customer and rep's locations.
Store the rep with that distance in the array along with a random number.
When finished looping, sort the array by distance & the random number.
Create a new Array B.
Loop 5 times, with index i.
For the rep at index i of A, place the contact information into B.
Return B.
function getSalesContacts(allReps, max, location) {
var available = [], rep;
for (var i = 0; i < allReps.length; i++) {
rep = allReps[i];
if (!rep.isAvailable()) {
continue;
}
available.push({
rep: rep,
dist: getDistance(location, rep.location),
rand: Math.random()
});
}
available.sort(function (a, b) {
return (a.dist - b.dist) || (a.rand - b.rand);
});
var results = [];
for (i = 0; i < Math.min(max, available.length); i++) {
rep = available[i].rep;
results.push({
name: rep.name,
phone: rep.phone,
email: rep.email,
location: rep.location,
hours: rep.hours
});
}
return results;
}
For every visitor to our site, display the contact information of five sales agents. Only show agents with open office hours. Of those, show the ones that are closest to the visitor's location first. For sales agents that share an office, list them in random order so they get equal chances for commissions.
Filter
For every visitor to our site, display the contact information of five sales agents. Only show agents with open office hours. Of those, show the ones that are closest to the visitor's location first. For sales agents that share an office, list them in random order so they get equal chances for commissions.
Map
For every visitor to our site, display the contact information of five sales agents. Only show agents with open office hours. Of those, show the ones that are closest to the visitor's location first. For sales agents that share an office, list them in random order so they get equal chances for commissions.
Sort
For every visitor to our site, display the contact information of five sales agents. Only show agents with open office hours. Of those, show the ones that are closest to the visitor's location first. For sales agents that share an office, list them in random order so they get equal chances for commissions.
Slice
function getSalesContacts(allReps, max, location) {
return allReps
.filter(rep => rep.isAvailable())
.map(rep => ({
rep: rep,
dist: getDistance(location, rep.location),
rand: Math.random()
}))
.sort((a, b) => (a.dist - b.dist) || (a.rand - b.rand))
.slice(0, max)
.map(d => {
rep = d.rep;
return {
name: rep.name,
phone: rep.phone,
email: rep.email,
location: rep.location,
hours: rep.hours
}
});
}
function getSalesContacts(allReps, max, location) {
return _(allReps)
.filter(rep => rep.isAvailable())
.map(rep => ({
rep: rep,
dist: getDistance(location, rep.location),
rand: Math.random()
}))
.sortByAll(['dist', 'rand'])
.take(max)
.pluck('rep')
.pick(['name', 'phone', 'email', 'location', 'hours']);
}
Non-blocking execution (besides alert, confirm, etc)
Single-threaded; uses event loop instead of parallel execution, so no need for locking primitives to protect mutable state.
Parallel code (Web Workers, etc) shares no mutable state; communication through message passing (Actor pattern)
Function expressions enable easy definition of callbacks.
$.ajax({
url: 'http://some.url/',
success: function (data, status, xhr) {
// Handle success
},
error: function (xhr, status, err) {
// Handle error
}
});
request.get('http://some.url/', function (err, result, body) {
if (err) {
// Handle error
} else {
// Handle result
}
});
function doDependentHttpRequests(cb) {
request.get('http://some.url/a', function (err, result, body) {
if (err) {
return void cb(err);
}
request.get('http://some.url/b/' + body.id, function (err, result, body) {
if (err) {
return void cb(err);
}
request.get('http://some.url/c/' + body.id, function (err, result, body) {
if (err) {
return void cb(err);
}
cb(null, result);
});
});
});
}
function getLatestArticle(userId, cb) {
async.auto({
user: function getUser (cb) {
request.get(`http://some.api/users/${userId}`, cb);
},
latestBlogArticles: ['user', function (cb, {user}) {
const blogTasks = user.blogIds.map(id => function (cb) {
async.waterfall([
function (cb) { request.get(`http://some.api/blogs/${id}`, cb); },
function (blog, cb) {
const latestArticleId = blog.articleIds[blog.articleIds.length - 1];
request.get(`http://some.api/articles/${latestArticleId}`, cb);
},
], cb);
});
async.parallelLimit(blogTasks, cb);
}],
latestArticle: ['latestBlogArticles', function (cb, {latestBlogArticles}) {
const latest = latestBlogArticles.sort((a, b) => a.timestamp - b.timestamp)[0];
cb(null, latest);
}]
}, function (err, {latestArticle}) {
cb(err, latestArticle);
});
}
function getLatestArticle(userId) {
return request.get(`http://some.api/users/${userId}`)
.then(([user]) => {
const latestArticlePromises = user.blogIds.map(id =>
request.get(`http://some.api/blogs/${id}`)
.then(blog => {
const latestArticleId = blog.articleIds[blog.articleIds.length - 1];
return request.get(`http://some.api/articles/${latestArticleId}`);
})
);
return Promise.all(latestArticlePromises);
})
.then(latestBlogArticles => {
return latestBlogArticles.sort((a, b) => a.timestamp - b.timestamp)[0];
});
}
// Still being standardized!
async function getLatestArticle(userId) {
const user = await request.get(`http://some.api/users/${userId}`);
const latestArticles = await* user.blogIds.map(id =>
request.get(`http://some.api/blogs/${id}`)
.then(blog => {
const latestArticleId = blog.articleIds[blog.articleIds.length - 1];
return request.get(`http://some.api/articles/${latestArticleId}`);
})
);
return latestArticles.sort((a, b) => a.timestamp - b.timestamp)[0];
}
webSocket.onopen = function (e) {
};
webSocket.onmessage = function (e) {
};
webSocket.onerror = function (e) {
};
webSocket.onclose = function (e) {
};
document
.getElementById('the-button')
.addEventListener('click', function (e) {
// ...
});
stream
.on('data', function (chunk) {
// Handle data
})
.on('error', function (err) {
// Handle error
})
.on('end', function () {
// Handle end
});
setTimeout(function () {
// ...
}, 1000);
setInterval(function () {
// ...
}, 1000);
requestAnimationFrame(function () {
// ...
});
setImmediate(function () {
// ...
});
Events: Moments in time + data
Async results = Moment in time + data
// Subscribe/observe
someStream
.onValue(...)
.onError(...)
.onEnd(...);
// Unsubscribe/unobserve
someStream
.offValue(...)
.offError(...)
.offEnd(...);
Time
circles.map(convertToTriangles)
Time
items.flatMap(i => i.getDerivedStream())
Time
faces.filter(f => f.isHappy())
Time
0
7
7
12
19
1
20
-8
12
2
14
-5
9
numbers.scan((sum, n) => sum + n, 0)
Time
noisyChannel.throttle(1000)
Time
twitchySignal.debounce(200)
Time
stream.zip(requests, responses)
Time
stream.merge([likes, shares, retweets])
Time
stream.combine([profile, ranking, history])
GO2L.INK/frpgame
State Server
Player Agent
Player Agent
Player Agent
Red: 12 Blue: 56
RxJs
Kefir
const connections = kefir.repeat(
() => kefir.stream(
emitter => {
setTimeout(() => {
const socket = new WebSocketClient(url);
socket.onopen = () => emitter.emit(socket);
socket.onerror = err => emitter.error(err);
socket.onclose = () => emitter.end();
}, 1000);
}
)
.endOnError()
);
const inboundMessages = connections
.flatMapLatest(s => kefir.stream(
emitter => s.onmessage = emitter.emit))
.map(e => JSON.parse(e.data));
const stateMessages = inboundMessages
.filter(msg => msg.type === 'state')
.map(msg => msg.state);
const timeDrift = inboundMessages
.map(msg => msg.timestamp - Date.now())
.slidingWindow(5, 1)
.map(lags =>
lags.reduce((sum, lag) => sum + lag, 0)
/ lags.length);
const state = kefir
.combine([stateMessages], [timeDrift])
.map(([state, drift]) => {
state.countdown = state.countdown - drift;
return state;
});
const animationFrames = kefir.repeat(
() => kefir.fromCallback(
cb => requestAnimationFrame(cb)
)
);
class PlayerState extends React.Component {
componentDidMount() {
kefir
.combine([animationFrames], [state])
.onValue(([_,s]) => this.setState(s));
}
render() {
return <PlayerInterface {...this.state}/>;
}
}
class PlayerInterface extends React.Component {
// ...
}
const dispatcher = new EventEmitter();
class PlayerInterface extends React.Component {
...
handleJoin(e) {
e.preventDefault();
dispatcher.emit(
'join',
{ name: this.state.name });
}
click(e) {
e.preventDefault();
dispatcher.emit('click', {});
}
}
const clicks = kefir
.fromEvents(dispatcher, 'click')
.map(e => ({ type: 'click' }));
const joins = kefir
.fromEvents(dispatcher, 'join')
.map(e => ({ type: 'join', name: e.name }));
const outboundMessages = kefir.merge([
clicks,
joins
]);
kefir
.combine([outboundMessages], [connections])
.onValue(([msg, socket]) => {
socket.send(JSON.stringify(msg));
});
const roster = dynamicValue([],
joinings, (players, req) => {
const message = req.message;
const name = getUniqueName(message.name, players);
const teamCounts = players.reduce(
(counts, p) => {
counts[p.team]++;
return counts
},
{ Red: 0, Blue: 0 });
const team =
teamCounts.Red <= teamCounts.Blue
? 'Red' : 'Blue';
return players.concat({
client: req.client,
id: message.id,
name: name,
team: team
});
},
leavings, (players, req) =>
players.filter(p => p.id !== req.player),
disconnections, (players, socket) =>
players.filter(p => p.client !== socket)
);
const timerSources = kefir.pool();
const completedTimers = timerSources.flatMap(
t => waitUntil(t.timestamp).map(() => t.status));
const canStart = roster.map(r => hasOpponents(r));
const waits = kefir
.merge([
serverStarts,
completedTimers.filter(s => s === 'finishedRound')])
.filterBy(canStart.map(b => !b))
.map(() => ({
status: 'finishedWait',
timestamp: Date.now() + 30000 }));
const countdowns = completedTimers
.filter(s => s === 'finishedWait')
.filterBy(canStart)
.map(() => ({
status: 'finishedCountdown',
timestamp: Date.now() + 5000 }));
const starts = completedTimers
.filter(s => s === 'finishedCountdown')
.map(() => ({
status: 'finishedRound',
timestamp: Date.now() + 5000 }));
timerSources.plug(waits);
timerSources.plug(countdowns);
timerSources.plug(starts);
const gameState = inboundStateServerMessages
.filter(m => m.type === 'state')
.map(({ state }) => {
let teamData = {
Red: { playerCount: 0, score: state.teamScores.Red },
Blue: { playerCount: 0, score: state.teamScores.Blue }
};
let playersById = {};
let topPlayer = null;
state.players.forEach(p => {
playersById[p.id] = p;
const team = teamData[p.team];
team.playerCount++;
topPlayer = (topPlayer && topPlayer.score > p.score) ? topPlayer : p;
});
return {
players: playersById,
teams: teamData,
status: state.status,
countdown: state.countdown,
topPlayer: topPlayer
};
})
.onValue(/* Eagerly consume */ () => { })
.toProperty();
Convert procedural, low-level event handlers to declarative, high-level operations using well-known operators from functional programming.
https://github.com/DullReferenceException/braziljs-frp-demo
@apocko