├── index.html
├── test
├── mocha.opts
├── state
│ ├── order.spec.js
│ ├── booking2.spec.js
│ └── booking.spec.js
├── proxy
│ └── index.spec.js
├── pubsub
│ └── index.spec.js
└── chain-of-resp
│ ├── index.spec.js
│ └── trafficLights.spec.js
├── .gitignore
├── react
├── tutorial
│ ├── crypto
│ │ ├── 1.js
│ │ └── index.html
│ └── clock
│ │ └── index.html
├── es5.html
└── es5time.html
├── strategy
├── samples
│ ├── conf.json
│ ├── loud.txt
│ ├── loud_mod.txt
│ └── conf_mod.json
├── strategies.js
├── main.js
└── config.js
├── state
├── strategies.js
├── booking2.js
├── order.js
└── booking.js
├── fp-light
├── util
│ └── fetch.js
├── switchcase
│ ├── 1.js
│ └── 2.js
├── rejectIfTrue
│ └── 1.js
├── composition
│ ├── 1.js
│ ├── 2.js
│ ├── 3.js
│ ├── 4.js
│ ├── 7.js
│ ├── 5.js
│ └── 6.js
├── sideffect
│ └── 1.js
├── examples
│ ├── refactor
│ │ ├── coin
│ │ │ ├── coin.js
│ │ │ └── refactored.js
│ │ └── google
│ │ │ ├── google.js
│ │ │ └── sampleData.json
│ └── promises.js
├── ramda
│ ├── pipe.js
│ └── explained.js
├── partial
│ ├── 3.js
│ ├── 2.js
│ └── 1.js
├── curry
│ ├── 1.js
│ └── 2.js
├── merge
│ └── 1.js
└── playground
│ └── 1.js
├── checkOut
├── order.js
├── checkout.spec.js
├── util.js
├── checkout.js
└── util.spec.js
├── .vscode
└── launch.json
├── conditionals
├── 1 - Array[includes].js
├── 5 - Array[every] and Array[some].js
├── 3 - Default params & destructuring.js
├── 4 - Object literals vs switch.js
└── 2 - Return early avoid nesting.js
├── package.json
├── proxy
└── index.js
├── async-await
├── blog.js
└── index.js
├── js-language
├── constructor_vs_classes
│ ├── example_2.js
│ ├── example_3.js
│ ├── example_1.js
│ └── example_4.js
└── this
│ └── this.js
├── reduce
└── example.js
├── objectAssign
└── styleGuide.md
├── styleguide
└── JavaScriptStyleGuide.md
├── redux
└── counter
│ ├── react.html
│ ├── modular.html
│ ├── react-reducer.html
│ ├── modular-reducer.html
│ ├── modular-redux.html
│ ├── modular-reducer2.html
│ ├── modular-redux2.html
│ └── modular-reducer3.html
├── modular
├── es6.html
├── es5.html
├── es6part2.html
└── react.html
├── chain-of-resp
├── trafficLights.js
├── getUser.js
└── index.js
└── pubsub
└── index.js
/index.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --exit
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/react/tutorial/crypto/1.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/strategy/samples/conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": "BLAH BLAH BLAH"
3 | }
--------------------------------------------------------------------------------
/strategy/samples/loud.txt:
--------------------------------------------------------------------------------
1 | TRANSFORM THIS TEXT FROM UPPERCASE TO LOWERCASE
--------------------------------------------------------------------------------
/strategy/samples/loud_mod.txt:
--------------------------------------------------------------------------------
1 | TRANSFORM THIS TEXT FROM UPPERCASE TO LOWERCASE timestamp: 42
--------------------------------------------------------------------------------
/strategy/samples/conf_mod.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": "BLAH BLAH BLAH",
3 | "title": "design patterns"
4 | }
--------------------------------------------------------------------------------
/state/strategies.js:
--------------------------------------------------------------------------------
1 | let strategies = module.exports = {}
2 |
3 | strategies.ausPost = {
4 | provider: 'ausPost',
5 | cost: 30.00,
6 | days: 1,
7 | pickup: false
8 | }
9 |
10 | strategies.sendle = {
11 | provider: 'sendle',
12 | cost: 15.00,
13 | days: 3,
14 | pickup: true
15 | }
--------------------------------------------------------------------------------
/strategy/strategies.js:
--------------------------------------------------------------------------------
1 | let strategies = module.exports = {}
2 |
3 | strategies.json = {
4 | deserialize: data => JSON.parse(data),
5 | serialize: data => JSON.stringify(data, null, ' ')
6 | }
7 |
8 | strategies.transform = {
9 | deserialize: data => data.toLowerCase(),
10 | serialize: data => data.toUpperCase() + ' timestamp: 42'
11 | }
--------------------------------------------------------------------------------
/fp-light/util/fetch.js:
--------------------------------------------------------------------------------
1 | let fetch = module.exports = {}
2 |
3 | let request = require('request')
4 |
5 | fetch = (url) => {
6 | return new Promise((resolve, reject) => {
7 | request(url, function (err, resp, body) {
8 | if (err) {
9 | reject(err)
10 | }
11 | resolve(JSON.parse(body))
12 | })
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/checkOut/order.js:
--------------------------------------------------------------------------------
1 | let order = {
2 | customer: 'howie',
3 | currency: 'AUD',
4 | lineItems: [
5 | {product: 'book', price: 1.50, quantity: 2, discount: 0.05},
6 | {product: 'shirt', price: 21.50, quantity: 1},
7 | {product: 'underwear', price: 5.50, quantity: 3},
8 | {product: 'shoes', price: 55.50, quantity: 1, discount: 0.1}
9 | ],
10 | address: '123 fake street',
11 | shippingZone: 'A'
12 | }
13 |
14 | module.exports = order
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Program",
11 | "program": "${workspaceFolder}/fp-light/partial/1.js"
12 | }
13 | ]
14 | }
--------------------------------------------------------------------------------
/strategy/main.js:
--------------------------------------------------------------------------------
1 | const Config = require('./config')
2 | const strategies = require('./strategies')
3 |
4 | const jsonConfig = new Config(strategies.json)
5 | jsonConfig.read('samples/conf.json')
6 | jsonConfig.set('title', 'design patterns')
7 | jsonConfig.save('samples/conf_mod.json')
8 |
9 | const textConfig = new Config(strategies.transform)
10 | textConfig.read('samples/loud.txt')
11 | textConfig.set('title', 'contrived example')
12 | textConfig.save('samples/loud_mod.txt')
--------------------------------------------------------------------------------
/checkOut/checkout.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let checkout = require('./checkout.js')
3 | let order = require('./order.js')
4 |
5 | describe('#checkout', function () {
6 | describe('#getReceipt', () => {
7 | it('should calculate breakdown of checkout items', () => {
8 | let input = checkout.getReceipt(order)
9 | let actual = {
10 | subTotal: '$96.50',
11 | subTotalWithDiscount: '$90.80',
12 | tax: '10.00%',
13 | shipping: '$5.00',
14 | totalCost: '$104.88'
15 | }
16 | expect(input).to.eql(actual)
17 | })
18 | })
19 | })
--------------------------------------------------------------------------------
/test/state/order.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let Order = require('../../state/order')
3 |
4 | describe('#Order', function(){
5 | it('should exist', () => expect(Order).to.not.be.undefined)
6 | it('should switch states when nextState is called', () => {
7 | let order = new Order()
8 |
9 | let first = order.state.name
10 | expect(first).to.equal('waiting for payment')
11 |
12 | order.nextState()
13 | let second = order.state.name
14 | expect(second).to.equal('shipping in progress')
15 |
16 | order.nextState()
17 | let final = order.state.name
18 | expect(final).to.equal('goods delivered')
19 | })
20 | })
--------------------------------------------------------------------------------
/test/proxy/index.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let { CarProxy, Driver } = require('../../proxy/index.js')
3 |
4 | describe('#CarProxy', function () {
5 | it('should run if driver is adult', () => {
6 | let adult = new Driver(21)
7 | let adultCar = new CarProxy(adult)
8 | let input = adultCar.drive()
9 | let actual = 'vroom vroom'
10 | expect(input).to.equal(actual)
11 | })
12 | it('should stall if driver is underage', () => {
13 | let child = new Driver(12)
14 | let childCar = new CarProxy(child)
15 | let input = childCar.drive()
16 | let actual = 'nar mate underage'
17 | expect(input).to.equal(actual)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/conditionals/1 - Array[includes].js:
--------------------------------------------------------------------------------
1 | // 1. Use Array.includes for multiple criteria
2 |
3 | // Check if fruit is red
4 |
5 | // Normal
6 | let checkFruit = (fruit) => {
7 | if (fruit === 'apple' || fruit === 'strawberry' || fruit === 'rasberry') {
8 | return 'red'
9 | } else {
10 | return 'not red'
11 | }
12 | }
13 |
14 | checkFruit('strawberry') // red
15 | checkFruit('poo') // not red
16 |
17 |
18 | // Use Array.includes
19 | let checkFruit2 = (fruit) => {
20 | let redFruits = ['apple', 'strawberry', 'rasberry', 'pomegranite', 'cherry']
21 | return redFruits.includes(fruit) ? 'red' : 'not red'
22 | }
23 |
24 | checkFruit2('cherry') // red
25 | checkFruit2('poo') // not red
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node_design_patterns",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "find ./test -name '*.spec.js' | xargs mocha -R spec"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/howardmann/node-design-patterns.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/howardmann/node-design-patterns/issues"
18 | },
19 | "homepage": "https://github.com/howardmann/node-design-patterns#readme",
20 | "devDependencies": {
21 | "chai": "^4.1.2",
22 | "lolex": "^2.3.1",
23 | "sinon": "^4.1.6"
24 | },
25 | "dependencies": {
26 | "axios": "^0.17.1",
27 | "lodash": "^4.17.10",
28 | "ramda": "^0.25.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/proxy/index.js:
--------------------------------------------------------------------------------
1 | // Proxy design pattern
2 | // Wrap the original factory function in a proxy with the same method name
3 | // Add any additional logic you need on top of the original logic
4 |
5 | let Car = function(){
6 | this.drive = () => 'vroom vroom'
7 | }
8 |
9 | let CarProxy = function(driver){
10 | this.driver = driver
11 |
12 | this.drive = () => {
13 | if (this.driver.age > 16)
14 | return new Car().drive()
15 |
16 | return 'nar mate underage'
17 | }
18 | }
19 |
20 | let Driver = function(age) {
21 | this.age = age
22 | }
23 |
24 | module.exports = {CarProxy, Driver}
25 |
26 | // let adult = new Driver(21)
27 | // let child = new Driver(12)
28 |
29 | // let adultCar = new CarProxy(adult)
30 | // let childCar = new CarProxy(child)
31 |
32 | // adultCar.drive() // vroom vroom
33 |
34 | // childCar.drive() // nar mate underage
35 |
--------------------------------------------------------------------------------
/state/booking2.js:
--------------------------------------------------------------------------------
1 | let strategies = require('./strategies')
2 |
3 | // Extension of booking using strategy
4 | let Booking = function (strategy) {
5 | this.strategy = strategy
6 | this.state = new Reserved()
7 |
8 | this.nextState = function () {
9 | this.state = this.state.next(this.strategy)
10 | }
11 | }
12 |
13 | // Forward progression
14 | let Reserved = function () {
15 | this.status = 'reservation made'
16 | this.next = function (strategy) {
17 | return new Paid(strategy)
18 | }
19 | }
20 |
21 | let Paid = function (strategy) {
22 | this.strategy = strategy
23 | this.status = Object.assign({msg: 'paid confirmation'}, this.strategy)
24 | this.next = function () {
25 | return new Redeemed()
26 | }
27 | }
28 |
29 | let Redeemed = function () {
30 | this.status = 'redeemed complete'
31 | this.next = function () {
32 | return this
33 | }
34 | }
35 |
36 | module.exports = Booking
--------------------------------------------------------------------------------
/conditionals/5 - Array[every] and Array[some].js:
--------------------------------------------------------------------------------
1 | // Use Array.every and Array.some for all/ partial criteria
2 |
3 | const fruits = [
4 | { name: 'apple', color: 'red' },
5 | { name: 'banana', color: 'yellow' },
6 | { name: 'grape', color: 'purple' }
7 | ];
8 |
9 | // Check if all fruits are red
10 |
11 | // Normal, set outside variables
12 | let checkFruits = (fruits) => {
13 | let isAllRed = true;
14 |
15 | // condition: all fruits must be red
16 | fruits.forEach(o => {
17 | if (!isAllRed) return false
18 | isAllRed = o.color === 'red'
19 | })
20 | return isAllRed
21 | }
22 |
23 | checkFruits(fruits) // false
24 |
25 | // Array.every
26 | let checkFruits2 = (fruits) => {
27 | return fruits.every(o => o.color === 'red')
28 | }
29 |
30 | checkFruits2(fruits) // false
31 |
32 |
33 | // Array.some to check if at least one is red
34 | let checkFruits3 = (fruits) => {
35 | return fruits.some(o => o.color === 'red')
36 | }
37 |
38 | checkFruits3(fruits) // true
--------------------------------------------------------------------------------
/fp-light/switchcase/1.js:
--------------------------------------------------------------------------------
1 | // Using if else
2 | let pickIf = (code) => {
3 | if (code === 'AU') {
4 | return 'Australia'
5 | } else if (code === 'US') {
6 | return 'United Sates'
7 | } else if (code === 'CN') {
8 | return 'China'
9 | } else if (code === 'NZ') {
10 | return 'New Zealand'
11 | } else {
12 | return 'Unknown'
13 | }
14 | }
15 |
16 | // Using switch
17 | let pickSwitch = (code) => {
18 | switch (code) {
19 | case 'AU':
20 | return 'Australia';
21 | case 'US':
22 | return 'United States';
23 | case 'UK':
24 | return 'United Kingdom';
25 | case 'CN':
26 | return 'China';
27 | case 'NZ':
28 | return 'New Zealand';
29 | default:
30 | return 'Unknown'
31 | }
32 | }
33 |
34 | // Use object picker
35 | let pickObj = (code) => {
36 | let cases = {
37 | 'AU': 'Australia',
38 | 'US': 'United States',
39 | 'UK': 'United Kingdom',
40 | 'CN': 'China',
41 | 'NZ': 'New Zealand'
42 | }
43 | return cases.hasOwnProperty(code) ? cases[code] : 'Unknown'
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/fp-light/rejectIfTrue/1.js:
--------------------------------------------------------------------------------
1 | // ## RejectIfTrue: Promise reject if function is true
2 | // Prepopulates function and rejectValue awaiting argument
3 | // If function returns true given the arg then reject the promise with the given rejectValue error msg
4 |
5 | const rejectIfTrue = (fn, rejectValue) => d => {
6 | return fn(d) ? Promise.reject(rejectValue) : d
7 | }
8 |
9 | // Dependency
10 | let _ = require('lodash')
11 |
12 | // Used from before in partial/1.js
13 | let fetchAll = () => Promise.resolve({
14 | name: 'Howie',
15 | looks: 'handsome',
16 | id: '123'
17 | })
18 | let postAPI = (id) => Promise.resolve({
19 | status: 'ok',
20 | id: '123'
21 | })
22 | let fetchAge = (id) => Promise.resolve({
23 | age: 15,
24 | id: '123'
25 | })
26 |
27 |
28 | fetchAll()
29 | .then(_.partialRight(_.pick, ['id', 'name']))
30 | .then(o => fetchAge(o.id))
31 | .then(rejectIfTrue(o => o.age < 18, 'You are underage'))
32 | .then(o => postAPI(o.id))
33 | .then(console.log) // Will log success message if overage
34 | .catch(console.error) // Will catch and throw error msg if underage
35 |
--------------------------------------------------------------------------------
/async-await/blog.js:
--------------------------------------------------------------------------------
1 | // Native implementation shortcoming SLOW, must author and rating is async
2 | const getBook = async bookName => {
3 | const book = await fetchBook(bookName);
4 |
5 | const author = await fetchAuthor(book.authorId);
6 | const rating = await fetchRating(book.id);
7 |
8 | return {
9 | ...book,
10 | author,
11 | rating
12 | };
13 | };
14 |
15 | // Promise.all fetches author and rating async but hard to read
16 | const getBook = async bookName => {
17 | const book = await fetchBook(bookName);
18 |
19 | return Promise.all([
20 | fetchAuthor(book.authorId),
21 | fetchRating(book.id)
22 | ]).then(results => ({
23 | ...book,
24 | author: results[0],
25 | rating: results[1]
26 | }));
27 | };
28 |
29 | // Best of both worlds using destructring, await and Promise.all
30 | const getBook = async bookName => {
31 | const book = await fetchBook(bookName);
32 |
33 | const [author, rating] = await Promise.all([
34 | fetchAuthor(book.authorId),
35 | fetchRating(book.id)
36 | ]);
37 |
38 | return {
39 | ...book,
40 | author,
41 | rating
42 | };
43 | };
--------------------------------------------------------------------------------
/js-language/constructor_vs_classes/example_2.js:
--------------------------------------------------------------------------------
1 | // Create a Person constructor that asks for name and strategy and then speaks
2 |
3 | // Example of two simple strategies
4 | let strategyUpper = {
5 | convert: (word) => word.toUpperCase()
6 | }
7 |
8 | let strategyLower = {
9 | convert: (word) => word.toLowerCase()
10 | }
11 |
12 |
13 | // #Constructor example
14 | let PersonConstructor = function(name, strategy) {
15 | this.name = name
16 | this.strategy = strategy
17 | this.speak = () => this.strategy.convert(this.name)
18 | }
19 |
20 | let howie = new PersonConstructor('Howie', strategyUpper)
21 | howie.speak() // HOWIE
22 |
23 | let felix = new PersonConstructor('Felix', strategyLower)
24 | felix.speak() // felix
25 |
26 | // #Class example
27 | class PersonClass {
28 | constructor(name, strategy) {
29 | this.name = name
30 | this.strategy = strategy
31 | }
32 | speak(){
33 | return this.strategy.convert(this.name)
34 | }
35 | }
36 |
37 | let hela = new PersonClass('Hela', strategyUpper)
38 | hela.speak() // HELA
39 |
40 | let felicity = new PersonClass('Felicity', strategyLower)
41 | felicity.speak() // felicity
42 |
--------------------------------------------------------------------------------
/state/order.js:
--------------------------------------------------------------------------------
1 | let Order = function(){
2 | this.state = new WaitingForPayment() // Set the initial state
3 | // Calls the next state and caches its value into this.state
4 | this.nextState = function(){
5 | this.state = this.state.next()
6 | }
7 | }
8 |
9 | let WaitingForPayment = function(){
10 | // Code logic goes here
11 | this.name = 'waiting for payment'
12 | // Returns the next state which gets cached by the Order object above
13 | this.next = function(){
14 | return new Shipping()
15 | }
16 | }
17 |
18 | let Shipping = function(){
19 | this.name = 'shipping in progress'
20 | this.next = function(){
21 | return new Delivered()
22 | }
23 | }
24 |
25 | let Delivered = function(){
26 | this.name = 'goods delivered'
27 | // Last in line returns itself
28 | this.next = function(){
29 | return this
30 | }
31 | }
32 |
33 | // let order = new Order()
34 | // console.log(order.state.name); // waiting for payment
35 | // order.nextState()
36 | // console.log(order.state.name); // shipping in progress
37 | // order.nextState()
38 | // console.log(order.state.name); // goods delivered
39 |
40 | module.exports = Order
--------------------------------------------------------------------------------
/conditionals/3 - Default params & destructuring.js:
--------------------------------------------------------------------------------
1 | // 3. Use default paramaters and destructuring
2 |
3 | // Normal
4 | let orderFruit = (fruit, quantity) => {
5 | let q = quantity || 1
6 | return `Order ${q} ${fruit}`
7 | }
8 |
9 | orderFruit('apple') // Order 1 apple
10 | orderFruit('apple', 2) // Order 2 apple
11 |
12 | // Use default param
13 | let orderFruit2 = (fruit, quantity = 1) => {
14 | return `Order ${quantity} ${fruit}`
15 | }
16 |
17 |
18 | orderFruit2('apple') // Order 1 apple
19 | orderFruit2('apple', 2) // Order 2 apple
20 |
21 |
22 | // Normal where fruit is object
23 | let buyFruit = (fruit) => {
24 | if (fruit && fruit.name) {
25 | let q = fruit.quantity ? fruit.quantity : 1
26 | return `Buy ${q} ${fruit.name}`
27 | } else {
28 | return 'No fruit'
29 | }
30 | }
31 |
32 | buyFruit({}) // No fruit
33 | buyFruit({name: 'apple'}) // Buy 1 apple
34 | buyFruit({name: 'apple', quantity: 2}) // Buy 2 apple
35 |
36 | // Use destructuring and default params
37 | let buyFruit2 = ({name, quantity = 1} = {}) => {
38 | return name ? `Buy ${quantity} ${name}` : 'No fruit'
39 | }
40 |
41 | buyFruit2({}) // No fruit
42 | buyFruit2({name: 'apple'}) // Buy 1 apple
43 | buyFruit2({name: 'apple', quantity: 2}) // Buy 2 apple
44 |
--------------------------------------------------------------------------------
/fp-light/composition/1.js:
--------------------------------------------------------------------------------
1 | // Basic composition: combining small functions that do small things
2 |
3 | let words = str => {
4 | return String(str)
5 | .toLowerCase()
6 | .split(/\s|\b/)
7 | .filter(function alpha(v) {
8 | return /^[\w]+$/.test(v);
9 | });
10 | }
11 |
12 | let unique = list => {
13 | var uniqList = [];
14 |
15 | for (let v of list) {
16 | // value not yet in the new list?
17 | if (uniqList.indexOf(v) === -1) {
18 | uniqList.push(v);
19 | }
20 | }
21 |
22 | return uniqList;
23 | }
24 |
25 | let text = "The quick brown fox jumped the fox brown fence";
26 |
27 | // 1. Breaking down task into steps. Find words from string then return unique
28 | let wordsFound = words(text);
29 | let wordsUsed = unique(wordsFound);
30 |
31 | wordsUsed; // [ 'the', 'quick', 'brown', 'fox', 'jumped', 'fence' ]
32 |
33 | // 2. We can skip the step by wraping the functions together
34 | let wordsUsed2 = unique(words(text))
35 | wordsUsed2 // [ 'the', 'quick', 'brown', 'fox', 'jumped', 'fence' ]
36 |
37 | // 3. Function composition using these two small functions to create a larger
38 | let uniqueWords = (str) => unique(words(text))
39 | let wordsUsed3 = uniqueWords(text)
40 | wordsUsed3 // [ 'the', 'quick', 'brown', 'fox', 'jumped', 'fence' ]
--------------------------------------------------------------------------------
/reduce/example.js:
--------------------------------------------------------------------------------
1 | // Example 1 - trasform array to object by property name
2 |
3 | let usersArr = [
4 | {name: 'howie', age: 21, sex: 'M'},
5 | {name: 'hela', age: 22, sex: 'F'},
6 | {name: 'felix', age: 3, sex: 'M'}
7 | ]
8 |
9 | let keyByUserNameReducer = (prev, user) => {
10 | return {...prev, [user.name]: user}
11 | }
12 |
13 | let users = usersArr.reduce(keyByUserNameReducer, {}) //?
14 | // {
15 | // howie: {
16 | // name: 'howie',
17 | // age: 21,
18 | // sex: 'M'
19 | // },
20 | // hela: {
21 | // name: 'hela',
22 | // age: 22,
23 | // sex: 'F'
24 | // },
25 | // felix: {
26 | // name: 'felix',
27 | // age: 3,
28 | // sex: 'M'
29 | // }
30 | // }
31 |
32 | // Example 2 - transform array to object and tally by property name
33 | let fruitsArr = [
34 | 'apple',
35 | 'banana',
36 | 'apple',
37 | 'apple',
38 | 'pear',
39 | 'banana',
40 | 'pineapple'
41 | ]
42 |
43 | let tallyFruitByNameReducer = (prev, fruit) => {
44 | let tally = {
45 | [fruit]: prev[fruit] ? prev[fruit] += 1 : 1
46 | }
47 | return {...prev, ...tally}
48 | }
49 |
50 | let fruitsTally = fruitsArr.reduce(tallyFruitByNameReducer, {}) //?
51 | // {
52 | // apple: 3,
53 | // banana: 2,
54 | // pear: 1,
55 | // pineapple: 1
56 | // }
--------------------------------------------------------------------------------
/fp-light/sideffect/1.js:
--------------------------------------------------------------------------------
1 | // ## SIDEEFFECT: Calls the function with arg and returns the original arg
2 | // This is helpful when using promises and wanting to call a sideffect but then
3 | // pass the original result further down the chain
4 |
5 | let sideEffect = fn => a => {
6 | fn(a) // process side-effects with arg
7 | return a // pass the arg further
8 | }
9 |
10 | let fetchAll = () => Promise.resolve({name: 'Howie', looks: 'handsome', id: '123'})
11 | let postAPI = (id) => Promise.resolve({status: 'ok', id:'123'})
12 | let fetchAge = (id) => Promise.resolve({age: 18, id:'123'})
13 |
14 | // 1. Before using helper
15 | // Problems. not clear where side effects are occuring
16 | fetchAll()
17 | .then(result => {
18 | // There are 2 side effects here but its not clear
19 | console.log(`Name returned: ${result.name}`)
20 | return postAPI(result.id)
21 | })
22 | .then(result => {
23 | let id = result.id
24 | return fetchAge(id)
25 | })
26 | .then(result => {
27 | console.log(`Age is: ${result.age}`);
28 | })
29 |
30 | // 2. Using helper
31 | // Clearer where sideffects are occuring
32 | fetchAll()
33 | .then(sideEffect(o => console.log(`Name returned: ${o.name}`)))
34 | .then(sideEffect(o => postAPI(o.id)))
35 | .then(o => fetchAge(o.id))
36 | .then(o => console.log(`Age is: ${o.age}`))
37 |
--------------------------------------------------------------------------------
/async-await/index.js:
--------------------------------------------------------------------------------
1 | let axios = require('axios')
2 |
3 | // Fetches the top 10 cryptocurrencies by market cap
4 | let fetchList = () => {
5 | let url = "https://api.coinmarketcap.com/v1/ticker/?limit=10"
6 | return axios.get(url)
7 | }
8 |
9 | // Fetches coin snapshot data of a given ticker symbol
10 | let fetchCoin = (ticker) => {
11 | let url = `https://www.cryptocompare.com/api/data/coinsnapshot/?fsym=${ticker}&tsym=USD`
12 | return axios.get(url)
13 | }
14 |
15 | // Best of readability and performance async await and Promise.all
16 | let getTop3Coins = async () => {
17 | // Use async await to firstly fetch the full list of coins and then pick out the top 3
18 | let topCoins = await fetchList()
19 | let firstTicker = topCoins.data[0].symbol
20 | let secondTicker = topCoins.data[1].symbol
21 | let thirdTicker = topCoins.data[2].symbol
22 |
23 | // Use Promise.all to fetch coin data and when done destructure into variables
24 | let [first, second, third] = await Promise.all([
25 | fetchCoin(firstTicker),
26 | fetchCoin(secondTicker),
27 | fetchCoin(thirdTicker
28 | )])
29 |
30 | // Pluck out what is neeeded and return
31 | return {
32 | [firstTicker]: first.data.Data,
33 | [secondTicker]: second.data.Data,
34 | [thirdTicker]: third.data.Data
35 | }
36 | }
37 |
38 | let x = getTop3Coins()
39 | x
--------------------------------------------------------------------------------
/checkOut/util.js:
--------------------------------------------------------------------------------
1 | let util = module.exports = {}
2 |
3 |
4 | util.calcSubTotal = (arr) => {
5 | let subTotalReducer = (prev, product) => {
6 | let subTotal = product.price * product.quantity
7 | return prev + subTotal
8 | }
9 |
10 | let initialValue = 0
11 | let result = arr.reduce(subTotalReducer, initialValue)
12 |
13 | return Number(result.toFixed(2))
14 | }
15 |
16 | util.calcSubTotalWithDiscount = (arr) => {
17 | let subTotalReducer = (prev, product) => {
18 | let subTotal = product.price * product.quantity
19 | let discount = product.discount ? (1 - product.discount) : 1
20 |
21 | return prev + (subTotal * discount)
22 | }
23 |
24 | let initialValue = 0
25 | let result = arr.reduce(subTotalReducer, initialValue)
26 |
27 | return Number(result.toFixed(2))
28 | }
29 |
30 | util.checkTax = (currency) => {
31 | const tax = {
32 | AUD: 0.1,
33 | USD: 0,
34 | EUR: 0.05,
35 | CNY: 0
36 | }
37 | return tax[currency] ? tax[currency] : 0
38 | }
39 |
40 | util.checkShipping = (zone) => {
41 | const shipping = {
42 | A: 5,
43 | B: 10,
44 | C: 15,
45 | D: 25
46 | }
47 | return shipping[zone] ? shipping[zone]: 5
48 | }
49 |
50 | util.toCurrency = (num) => {
51 | return `$${num.toFixed(2)}`
52 | }
53 |
54 | util.toPercent = (num) => {
55 | return `${(num*100).toFixed(2)}%`
56 | }
--------------------------------------------------------------------------------
/conditionals/4 - Object literals vs switch.js:
--------------------------------------------------------------------------------
1 | // 4. Use object literals over switch statement
2 |
3 | // Normal switch base to find fruits in color
4 | let findFruit = (color) => {
5 | // use switch case to find fruits in color
6 | switch (color) {
7 | case 'red':
8 | return ['apple', 'strawberry'];
9 | case 'yellow':
10 | return ['banana', 'pineapple'];
11 | case 'purple':
12 | return ['grape', 'plum'];
13 | default:
14 | return [];
15 | }
16 | }
17 |
18 | findFruit('red') // ['apple', 'strawberry']
19 |
20 | // Use object literal
21 | let findFruit2 = (color) => {
22 | const colors = {
23 | red: ['apple', 'strawberry'],
24 | yellow: ['banana', 'pineapple'],
25 | purple: ['grape', 'plum']
26 | }
27 | return colors[color] || []
28 | }
29 |
30 | findFruit2('red') // ['apple', 'strawberry']
31 |
32 | // Refactor into object literal and filter
33 | let findFruit3 = (color) => {
34 | const fruits = [
35 | { name: 'apple', color: 'red' },
36 | { name: 'strawberry', color: 'red' },
37 | { name: 'banana', color: 'yellow' },
38 | { name: 'pineapple', color: 'yellow' },
39 | { name: 'grape', color: 'purple' },
40 | { name: 'plum', color: 'purple' }
41 | ];
42 |
43 | return fruits
44 | .filter(o => o.color === color)
45 | .map(o => o.name)
46 | }
47 |
48 | findFruit3('red') // ['apple', 'strawberry']
49 |
--------------------------------------------------------------------------------
/js-language/this/this.js:
--------------------------------------------------------------------------------
1 | // Using ES5 bindings for this to get around lexical scope
2 | var Adder = function() {
3 | this.sum = 0,
4 | this.add = function(numbers){
5 | numbers.forEach(function(n) {
6 | this.sum += n
7 | }, this)
8 | }
9 | this.calc = function(numbers){
10 | return this.nested.multiplyBy(numbers, 3)
11 | }
12 | this.nested = {
13 | multiplyBy: function (numbers, multiple) {
14 | let result = numbers.map(function(n){
15 | return n * multiple
16 | })
17 | this.sum = result
18 | }.bind(this)
19 | }
20 | }
21 |
22 | var adder = new Adder()
23 | adder.add([1,2,3,4,5])
24 | var x = adder.sum // 15
25 |
26 | adder.calc([1,2,3])
27 | var y = adder.sum // [3, 6, 9]
28 |
29 |
30 | // Same as above using ES6 arrow functions to get around lexical this scope
31 | var AdderES6 = function () {
32 | this.sum = 0,
33 | this.add = function (numbers) {
34 | numbers.forEach(n => this.sum +=n)
35 | }
36 | this.calc = function (numbers) {
37 | return this.nested.multiplyBy(numbers, 3)
38 | }
39 | this.nested = {
40 | multiplyBy: (numbers, multiple) => {
41 | let result = numbers.map(n => n * multiple)
42 | this.sum = result
43 | }
44 | }
45 | }
46 |
47 | var adder = new AdderES6()
48 | adder.add([1, 2, 3, 4, 5])
49 | var x = adder.sum // 15
50 |
51 | adder.calc([1, 2, 3])
52 | var y = adder.sum // [3, 6, 9]
53 |
--------------------------------------------------------------------------------
/fp-light/composition/2.js:
--------------------------------------------------------------------------------
1 | // Example 2: we want to sum the total seats that are not success managed
2 |
3 | let data = [
4 | {success: true, seats: 100},
5 | {success: false, seats: 30},
6 | {success: false, seats: 20},
7 | {success: false, seats: 30},
8 | {success: true, seats: 80},
9 | {success: false, seats: 20},
10 | {success: true, seats: 10},
11 | {success: false, seats: 30}
12 | ]
13 |
14 | // We will break this task into three smaller functions
15 | let filterSuccess = (arr, boolean = true) => {
16 | return arr.filter( el => boolean ? el.success : !el.success)
17 | }
18 |
19 | let getProperty = (arr, prop) => {
20 | return arr.map( el => el[prop])
21 | }
22 |
23 | let sum = (arr) => {
24 | return arr.reduce((el, tally) => {
25 | return el + tally
26 | }, 0)
27 | }
28 |
29 |
30 | // 1. Answer it using variables
31 | let notSuccess = filterSuccess(data, false)
32 | let seatsNotSuccess = getProperty(notSuccess, 'seats')
33 | let total = sum(seatsNotSuccess)
34 | total // 130
35 |
36 | // 2. Answer it in one go (start from inside and then go out)
37 | let total2 = sum(getProperty(filterSuccess(data, false), 'seats'))
38 | total2 // 130
39 |
40 | // 3. Compose a new function: same as above, but makes it cleaner with one fn
41 | let totalNonSuccess = (arr) => {
42 | return sum(getProperty(filterSuccess(arr, false), 'seats'))
43 | }
44 | let total3 = totalNonSuccess(data)
45 | total3 //130
46 |
47 |
48 |
--------------------------------------------------------------------------------
/fp-light/switchcase/2.js:
--------------------------------------------------------------------------------
1 | // FP helper (same as 1.js last example but creating a util)
2 | let switchCase = cases => defaultCase => code =>
3 | cases.hasOwnProperty(code) ? cases[code] : defaultCase
4 |
5 | const cases = {
6 | 'AU': 'Australia',
7 | 'US': 'United States',
8 | 'UK': 'United Kingdom',
9 | 'CN': 'China',
10 | 'NZ': 'New Zealand'
11 | }
12 |
13 | const defaultCase = 'Unknown'
14 |
15 | let pickCountry = switchCase(cases)(defaultCase)
16 |
17 | // Using rambda cond function
18 | // Takes an array of [predicate, transformer] function pairs
19 | let R = require('ramda')
20 |
21 | let pickCountryR = R.cond([
22 | [R.equals('AU'), R.always('Australia')],
23 | [R.equals('US'), R.always('United States')],
24 | [R.equals('UK'), R.always('United Kingdom')],
25 | [R.equals('CN'), R.always('China')],
26 | [R.equals('NZ'), () => 'New Zealand'],
27 | [R.is(String), () => 'Unknown']
28 | ])
29 |
30 | // Advantage of using ramda cond is the ability to transform based on the arg
31 |
32 | let echoCountry = R.cond([
33 | [R.equals('AU'), (d) => `${d} stands for Straya`],
34 | [R.equals('US'), (d) => `${d}A ${d}A ${d}A`],
35 | [R.equals('UK'), (d) => `FC${d}`],
36 | [R.equals('CN'), R.always('China')],
37 | [R.equals('NZ'), () => 'New Zealand'],
38 | [R.is(String), () => 'Unknown']
39 | ])
40 |
41 | echoCountry('AU') // AU stands for Straya
42 | echoCountry('US') // USA USA USA
43 | echoCountry('UK') // FCUK
44 |
--------------------------------------------------------------------------------
/test/state/booking2.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let Booking = require('../../state/booking2')
3 | let strategies = require('../../state/strategies')
4 |
5 | describe('#Booking2', function () {
6 | it('should exist', () => expect(Booking).to.not.be.undefined)
7 | it('should use the relevant strategy for payment options', () => {
8 | let sendle = strategies.sendle
9 | let booking = new Booking(sendle)
10 |
11 | let first = booking.state.status
12 | expect(first).to.equal('reservation made')
13 |
14 | booking.nextState()
15 | let second = booking.state.status
16 | let actual = {
17 | msg: 'paid confirmation',
18 | provider: 'sendle',
19 | cost: 15,
20 | days: 3,
21 | pickup: true
22 | }
23 | expect(second).to.eql(actual)
24 | booking.nextState()
25 | })
26 | it('should show different payment options when using different strategy', () => {
27 | let ausPost = strategies.ausPost
28 | let booking = new Booking(ausPost)
29 |
30 | let first = booking.state.status
31 | expect(first).to.equal('reservation made')
32 |
33 | booking.nextState()
34 | let second = booking.state.status
35 | let actual = {
36 | msg: 'paid confirmation',
37 | provider: 'ausPost',
38 | cost: 30,
39 | days: 1,
40 | pickup: false
41 | }
42 | expect(second).to.eql(actual)
43 | booking.nextState()
44 | })
45 |
46 |
47 | })
--------------------------------------------------------------------------------
/fp-light/examples/refactor/coin/coin.js:
--------------------------------------------------------------------------------
1 | let coin = module.exports = {}
2 |
3 | // Dependencies
4 | let get = require('lodash/get');
5 | let axios = require('axios');
6 | let values = require('lodash/values');
7 |
8 | coin.getPrices = (request = axios) => {
9 | let url = "https://api.coinmarketcap.com/v1/ticker/?limit=30"
10 | return request.get(url)
11 | }
12 |
13 | coin.parseSingleTicker = (obj) => {
14 | let {
15 | symbol,
16 | price_usd,
17 | market_cap_usd,
18 | percent_change_24h,
19 | percent_change_7d
20 | } = obj
21 | return {
22 | ticker: symbol,
23 | currency: "USD",
24 | price: Number(price_usd),
25 | mktcap: Number(market_cap_usd),
26 | changePctDay: Number(percent_change_24h) / 100,
27 | changePctWeek: Number(percent_change_7d) / 100
28 | }
29 | }
30 |
31 | coin.parseTickers = (data) => {
32 | let transformed = data.map(ticker => coin.parseSingleTicker(ticker))
33 | let transformedSorted = transformed.sort((a, b) => b.mktcap - a.mktcap)
34 | return transformedSorted
35 | }
36 |
37 | coin.fetchTickers = (request = axios) => {
38 | return new Promise((resolve, reject) => {
39 | coin.getPrices(request)
40 | .then(payload => {
41 | let data = payload.data
42 | let result = coin.parseTickers(data)
43 | resolve(result)
44 | })
45 | .catch(err => {
46 | reject(err)
47 | })
48 | })
49 | }
50 |
51 | // Use case
52 | // coin.fetchTickers().then(data => {
53 | // data
54 | // })
--------------------------------------------------------------------------------
/fp-light/ramda/pipe.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | let data = [
4 | { university: true, salary: 300, age: 42, gender: 'male' },
5 | { university: true, salary: 200, age: 35, gender: 'female' },
6 | { university: false, salary: 80, age: 34, gender: 'male' },
7 | { university: true, salary: 300, age: 23, gender: 'female' },
8 | { university: true, salary: 250, age: 52, gender: 'female' },
9 | { university: true, salary: 100, age: 31, gender: 'female' },
10 | { university: true, salary: 200, age: 45, gender: 'male' },
11 | { university: true, salary: 70, age: 18, gender: 'male' },
12 | { university: false, salary: 50, age: 21, gender: 'male' },
13 | { university: true, salary: 30, age: 43, gender: 'female' }
14 | ]
15 |
16 | // Helpers without using Ramda
17 | // Alternatives for Ramda are R.propEq and R.prop
18 | let propEq = (prop, value) =>
19 | o => o[prop] === value ? true : false
20 |
21 | let prop = (prop) =>
22 | o => o[prop]
23 |
24 | // Calc average salary of male university graduates
25 | let average = (arr) => arr.reduce((sum, tally) => sum + tally, 0) / arr.length
26 |
27 | let calcAvg = (gender) =>
28 | R.pipe(
29 | R.filter(propEq('university', true)),
30 | R.filter(propEq('gender', gender)),
31 | R.map(prop('salary')),
32 | average
33 | )
34 |
35 | let calcMaleAvg = calcAvg('male')
36 | let calcFemaleAvg = calcAvg('female')
37 |
38 | let maleAvg = calcMaleAvg(data)
39 | maleAvg // 190
40 |
41 | let femaleAvg = calcFemaleAvg(data)
42 | femaleAvg // 176
43 |
44 |
--------------------------------------------------------------------------------
/objectAssign/styleGuide.md:
--------------------------------------------------------------------------------
1 | # JavaScript Styleguide - Notable mentions
2 |
3 | ## Objects
4 | * Use ES6 object spread operator over `Object.assign` to copy objects
5 | ```javascript
6 | // bad
7 | const original = {a: 1, b:2}
8 | const copy = Object.assign(original, {c:3}) // this mutates original
9 | original // => {a:1, b:2, c:3}
10 | copy // => {a:1, b:2, c:3}
11 |
12 | // good
13 | const original = {a: 1, b:2}
14 | const copy = Object.assign({}, original, {c:3})
15 | original // => {a:1, b:2}
16 | copy // => {a:1, b:2, c:3}
17 |
18 | // better
19 | const original = {a: 1, b:2}
20 | const copy = {...original, c: 3}
21 | original // => {a:1, b:2}
22 | copy // => {a:1, b:2, c:3}
23 |
24 | // better when copying object with multiple properties
25 | const original = {a:1, b:2}
26 | const add = {c: 3, d: 4}
27 | const copy = {...original, ...add}
28 | original // => {a:1, b:2}
29 | add // => {c:3, d:4}
30 | copy // => {a:1, b:2, c:3, d:4}
31 |
32 | ```
33 |
34 | * Use the rest operator to get a new object with certain properties omitted
35 | ```javascript
36 | // bad
37 | const original = {a: 1, b:2}
38 | const copy = {...original, c: 3}
39 | delete copy.a // this mutates the copy
40 | original // => {a:1, b:2}
41 | copy // => {b:2, c:3}
42 |
43 | // good
44 | const original = {a: 1, b:2}
45 | const copy = {...original, c: 3}
46 | const {a, ...noA} = copy // no mutation. copies value of a into a variable and rest into new noA variable
47 | original // => {a:1, b:2}
48 | copy // => {a:1, b:2, c:3}
49 | noA // => {b:2, c:3}
50 | a // => 1
51 | ```
--------------------------------------------------------------------------------
/conditionals/2 - Return early avoid nesting.js:
--------------------------------------------------------------------------------
1 | // 2. Less nesting return early
2 |
3 | // Fruit must be String, check if just red or red and berry
4 |
5 | // Normal
6 | let checkFruit = (fruit) => {
7 | let redFruits = ['apple', 'strawberry', 'rasberry', 'pomegranite', 'cherry']
8 | let isBerry = (str) => /berry/.test(str)
9 | if (typeof fruit === 'string') {
10 | if (redFruits.includes(fruit)) {
11 | if (isBerry(fruit)){
12 | return 'red berry'
13 | } else {
14 | return 'red fruit'
15 | }
16 | } else {
17 | return 'not red fruit'
18 | }
19 | } else {
20 | return 'not a string'
21 | }
22 | }
23 |
24 | checkFruit('strawberry') // red berry
25 |
26 | // Return early less nesting
27 | // Start with most specific and then go general (e..g 1. check string, 2. check red berry, 3. check red fruit)
28 | let checkFruit2 = (fruit) => {
29 | let redFruits = ['apple', 'strawberry', 'rasberry', 'pomegranite', 'cherry']
30 | let isBerry = (str) => /berry/.test(str)
31 |
32 | // condition 1: return early
33 | if (typeof fruit !== 'string') return 'not a string'
34 |
35 | // condition 2: check if red and berry
36 | if (redFruits.includes(fruit) && isBerry(fruit)) return 'red berry'
37 |
38 | // condition 3: check if red fruit or not
39 | return (redFruits.includes(fruit)) ? 'red fruit' : 'not red fruit'
40 | }
41 |
42 | checkFruit2(42) // not a string
43 | checkFruit2('strawberry') // red berry
44 | checkFruit2('apple') // red fruit
45 | checkFruit2('halleberry') // not red fruit
--------------------------------------------------------------------------------
/styleguide/JavaScriptStyleGuide.md:
--------------------------------------------------------------------------------
1 | # JavaScript Styleguide - Notable mentions
2 |
3 | ## Objects
4 | * Use ES6 object spread operator over `Object.assign` to copy objects
5 | ```javascript
6 | // bad
7 | const original = {a: 1, b:2}
8 | const copy = Object.assign(original, {c:3}) // this mutates original
9 | original // => {a:1, b:2, c:3}
10 | copy // => {a:1, b:2, c:3}
11 |
12 | // good
13 | const original = {a: 1, b:2}
14 | const copy = Object.assign({}, original, {c:3})
15 | original // => {a:1, b:2}
16 | copy // => {a:1, b:2, c:3}
17 |
18 | // better
19 | const original = {a: 1, b:2}
20 | const copy = {...original, c: 3}
21 | original // => {a:1, b:2}
22 | copy // => {a:1, b:2, c:3}
23 |
24 | // better when copying object with multiple properties
25 | const original = {a:1, b:2}
26 | const add = {c: 3, d: 4}
27 | const copy = {...original, ...add}
28 | original // => {a:1, b:2}
29 | add // => {c:3, d:4}
30 | copy // => {a:1, b:2, c:3, d:4}
31 |
32 | ```
33 |
34 | * Use the rest operator to get a new object with certain properties omitted
35 | ```javascript
36 | // bad
37 | const original = {a: 1, b:2}
38 | const copy = {...original, c: 3}
39 | delete copy.a // this mutates the copy
40 | original // => {a:1, b:2}
41 | copy // => {b:2, c:3}
42 |
43 | // good
44 | const original = {a: 1, b:2}
45 | const copy = {...original, c: 3}
46 | const {a, ...noA} = copy // no mutation. copies value of a into a variable and rest into new noA variable
47 | original // => {a:1, b:2}
48 | copy // => {a:1, b:2, c:3}
49 | noA // => {b:2, c:3}
50 | a // => 1
51 | ```
--------------------------------------------------------------------------------
/checkOut/checkout.js:
--------------------------------------------------------------------------------
1 | let checkout = module.exports = {}
2 | let util = require('./util.js')
3 | let order = require('./order.js')
4 |
5 |
6 | // Objective calculate key receipt data info from order. Transform order to receipt
7 |
8 | // order input =>
9 | // let order = {
10 | // customer: 'howie',
11 | // currency: 'AUD',
12 | // lineItems: [
13 | // {product: 'book', price: 1.50, quantity: 2, discount: 0.05},
14 | // {product: 'shirt', price: 21.50, quantity: 1},
15 | // {product: 'underwear', price: 5.50, quantity: 3},
16 | // {product: 'shoes', price: 55.50, quantity: 1, discount: 0.1}
17 | // ],
18 | // address: '123 fake street',
19 | // shippingZone: 'A'
20 | // }
21 |
22 |
23 | // checkout.getReceipt(order) result =>
24 | // {
25 | // subTotal: '$96.50',
26 | // subTotalWithDiscount: '$90.80',
27 | // tax: '10.00%',
28 | // shipping: '$5.00',
29 | // totalCost: '$104.88'
30 | // }
31 |
32 | checkout.getReceipt = (order) => {
33 | let subTotal = util.calcSubTotal(order.lineItems)
34 | let subTotalWithDiscount = util.calcSubTotalWithDiscount(order.lineItems)
35 | let tax = util.checkTax(order.currency)
36 | let shipping = util.checkShipping(order.shippingZone)
37 | let totalCost = Number(((subTotalWithDiscount * (1+ tax)) + shipping).toFixed(2))
38 |
39 | return {
40 | subTotal: util.toCurrency(subTotal),
41 | subTotalWithDiscount: util.toCurrency(subTotalWithDiscount),
42 | tax: util.toPercent(tax),
43 | shipping: util.toCurrency(shipping),
44 | totalCost: util.toCurrency(totalCost)
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/test/pubsub/index.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let { Product, EmailAlert } = require('../../pubsub/index.js')
3 |
4 |
5 | describe('#pubsub', function () {
6 | it('should alert when there is a price drop', () => {
7 | let book = new Product('book', 30)
8 | let johnAlert = new EmailAlert('john@email.com')
9 | johnAlert.setAlert(20, 'book')
10 | book.setPrice(15)
11 |
12 | let input = johnAlert.msg
13 | let actual = 'Email to john@email.com: book price below 20 now at 15'
14 | expect(input).to.equal(actual)
15 | })
16 | it('should alert to multiple emails when there is a price drop for single product', () => {
17 | let book = new Product('book', 30)
18 | let johnAlert = new EmailAlert('john@email.com')
19 | let billAlert = new EmailAlert('bill@email.com')
20 | let kateAlert = new EmailAlert('kate@email.com')
21 | let mindyAlert = new EmailAlert('mindy@email.com')
22 | johnAlert.setAlert(20, 'book')
23 | billAlert.setAlert(25, 'book')
24 | kateAlert.setAlert(17, 'book')
25 | mindyAlert.setAlert(5, 'book')
26 |
27 | // Trigger event
28 | book.setPrice(15)
29 |
30 | let input = [
31 | johnAlert.msg,
32 | billAlert.msg,
33 | kateAlert.msg,
34 | mindyAlert.msg
35 | ]
36 | let actual = [
37 | 'Email to john@email.com: book price below 20 now at 15',
38 | 'Email to bill@email.com: book price below 25 now at 15',
39 | 'Email to kate@email.com: book price below 17 now at 15',
40 | null
41 | ]
42 | expect(input).to.eql(actual)
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/test/state/booking.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let Booking = require('../../state/booking')
3 |
4 | describe('#Booking', function () {
5 | it('should exist', () => expect(Booking).to.not.be.undefined)
6 | it('should switch states when nextState is called', () => {
7 | let booking = new Booking()
8 |
9 | let first = booking.state.status
10 | expect(first).to.equal('reservation made')
11 |
12 | booking.nextState()
13 | let second = booking.state.status
14 | expect(second).to.equal('paid confirmation')
15 |
16 | booking.nextState()
17 | let final = booking.state.status
18 | expect(final).to.equal('redeemed complete')
19 | })
20 |
21 | it('should be able to cancel reservation after reservation is made', () => {
22 | let booking = new Booking()
23 |
24 | let first = booking.state.status
25 | expect(first).to.equal('reservation made')
26 |
27 | booking.exitState()
28 | let second = booking.state.status
29 | expect(second).to.equal('cancel reservation')
30 |
31 | booking.nextState()
32 | let final = booking.state.status
33 | expect(final).to.equal('cancel reservation')
34 | })
35 |
36 | it('should refund payment after payment is made', () => {
37 | let booking = new Booking()
38 |
39 | let first = booking.state.status
40 | expect(first).to.equal('reservation made')
41 |
42 | booking.nextState()
43 | let second = booking.state.status
44 | expect(second).to.equal('paid confirmation')
45 |
46 | booking.exitState()
47 | let final = booking.state.status
48 | expect(final).to.equal('refund processed')
49 | })
50 | })
--------------------------------------------------------------------------------
/js-language/constructor_vs_classes/example_3.js:
--------------------------------------------------------------------------------
1 | // Same as example as #1 calculator but using functional composition
2 | // Separate out methods into separate functions that return method objects
3 | // Advantage of this is it creates reusable functions that other constructors can use
4 |
5 | let makeAdder = () => {
6 | return {
7 | add: (a,b) => a + b
8 | }
9 | }
10 |
11 | // sugar syntax using ({}) to return an object
12 | let makeSubtract = () => ({
13 | subtract: (a,b) => a - b
14 | })
15 |
16 |
17 | let Calculator = function () {
18 | // Save any state into a state object (convention)
19 | let state = {
20 | cache: null
21 | }
22 |
23 | // Separate method for anything affecting state, returns a new object method with public method of `save`
24 | let makeStateSetter = () => ({
25 | save: (num) => state.cache = num
26 | })
27 |
28 | // Publically exposed object with methods attaching to initial state object
29 | return Object.assign(state,
30 | makeStateSetter(),
31 | makeAdder(),
32 | makeSubtract()
33 | )
34 | }
35 |
36 | let calc = new Calculator()
37 | let result = calc.add(1,2) // 3
38 | calc.save(result)
39 | let p = calc.cache // 3
40 |
41 | // Example where we can reuse the adder function in a separate piece of logic
42 | let Cashier = function(){
43 | let state = {
44 | total: null,
45 | tax: 1.1
46 | }
47 | let makeStateSetter = () => ({
48 | checkOut: (num) => state.total = num * state.tax
49 | })
50 |
51 | return Object.assign(state,
52 | makeAdder(),
53 | makeStateSetter()
54 | )
55 | }
56 |
57 | let cashier = new Cashier()
58 | let subTotal = cashier.add(40,2) // 42
59 | cashier.checkOut(subTotal)
60 | cashier.total // 46.2
--------------------------------------------------------------------------------
/redux/counter/react.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Counter using React JS
12 |
13 |
14 |
15 |
16 |
17 |
18 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/js-language/constructor_vs_classes/example_1.js:
--------------------------------------------------------------------------------
1 | // Create a simple calculator function that takes two numbers
2 |
3 | // #Constructor function example
4 | let CalculatorConstructor = function (){
5 | // Save results in memory. Note: do this to show that it creates a new instance of calculator
6 | this.cache = null
7 | this.save = (num) => {
8 | this.cache = num
9 | return `Saved ${this.cache}`
10 | }
11 |
12 | // Logic
13 | this.add = (a,b) => a + b
14 | this.subtract = (a,b) => a - b
15 | }
16 |
17 | let calc1 = new CalculatorConstructor()
18 | let result1 = calc1.add(40,2)
19 | calc1.save(result1)
20 | calc1.cache // 42
21 |
22 | let calc2 = new CalculatorConstructor()
23 | let result2 = calc1.subtract(40, 2)
24 | calc2.save(result2)
25 | calc2.cache // 38
26 |
27 | // calc1 result does not get mutated as they are new instances
28 | calc1.cache // 42
29 |
30 | // #Class example
31 | // Note similar to constructor function example, really only syntax sugar, put any `this` properties into the constructor, which will also instantiate any initial params. Methods have a slightly different syntax without `this` and cannot use arrow functions
32 | class CalculatorClass {
33 | constructor(){
34 | this.cache = null
35 | }
36 |
37 | save(num) {
38 | this.cache = num
39 | return `Saved ${this.cache}`
40 | }
41 |
42 | add(a,b) {
43 | return a + b
44 | }
45 | subtract(a,b) {
46 | return a - b
47 | }
48 | }
49 |
50 | // Class works same as above
51 | let calc3 = new CalculatorClass()
52 | let result3 = calc3.add(40,2) // 42
53 | calc3.save(result3)
54 | calc3.cache // 42
55 |
56 | let calc4 = new CalculatorClass()
57 | let result4 = calc4.subtract(40,2) // 38
58 | calc4.save(result4)
59 | calc4.cache // 38
60 |
61 |
--------------------------------------------------------------------------------
/fp-light/composition/3.js:
--------------------------------------------------------------------------------
1 | // Compose function utility which takes two functions and makes one function
2 | let compose2 = (fn2, fn1) => {
3 | return function composed(origValue) {
4 | return fn2(fn1(origValue));
5 | };
6 | }
7 |
8 | // Example from 1.js
9 | let words = str => {
10 | return String(str)
11 | .toLowerCase()
12 | .split(/\s|\b/)
13 | .filter(function alpha(v) {
14 | return /^[\w]+$/.test(v);
15 | });
16 | }
17 |
18 | let unique = list => {
19 | var uniqList = [];
20 |
21 | for (let v of list) {
22 | // value not yet in the new list?
23 | if (uniqList.indexOf(v) === -1) {
24 | uniqList.push(v);
25 | }
26 | }
27 |
28 | return uniqList;
29 | }
30 |
31 | // Additional function
32 | let capitalize = list => {
33 | return list.map(el => el.toUpperCase())
34 | }
35 |
36 | let text = "The quick brown fox jumped the fox brown fence";
37 |
38 | // Manually using compose
39 | let uniqueWords = (str) => unique(words(str))
40 | let result = uniqueWords(text)
41 | result // [ 'the', 'quick', 'brown', 'fox', 'jumped', 'fence' ]
42 |
43 |
44 | // 1. Let's use our compose2 function to replicate the above
45 | let uniqueWords2 = compose2(unique, words)
46 | let result2 = uniqueWords2(text)
47 | result2 // [ 'the', 'quick', 'brown', 'fox', 'jumped', 'fence' ]
48 |
49 | // 2. We can use our same compose2 function to combine two different functions
50 | let uppercaseWords = compose2(capitalize, words)
51 | let result3 = uppercaseWords(text)
52 | result3 // ['THE', 'QUICK', 'BROWN', 'FOX', 'JUMPED', 'THE', 'FOX', 'BROWN', 'FENCE']
53 |
54 | // Under the hood of 2. above is doing
55 | let uppercaseWords2 = (str) => capitalize(words(str))
--------------------------------------------------------------------------------
/fp-light/partial/3.js:
--------------------------------------------------------------------------------
1 | // From getify FP light https://github.com/getify/Functional-Light-JS/blob/master/manuscript/ch3.md/#chapter-3-managing-function-inputs
2 | // #Partial application is a technique
3 | // for reducing the arity(that is, the expected number of arguments to a
4 | // function) by creating a new function where some of the arguments are preset.
5 |
6 | let request = require('request')
7 |
8 | // Custom fetch
9 | let fetch = (url) => {
10 | return new Promise((resolve, reject) => {
11 | request(url, function (err, resp, body) {
12 | if (err) { reject(err) }
13 | resolve(JSON.parse(body))
14 | })
15 | })
16 | }
17 |
18 | // Custom fetcher which returns ticker and currency pairs
19 | let getTickerPrices = (ticker, compareArr) => {
20 | let currencyArr = compareArr.join(',')
21 | let url = `https://min-api.cryptocompare.com/data/price?fsym=${ticker}&tsyms=${ticker},${currencyArr}`
22 | return fetch(url)
23 | }
24 |
25 | // Using a partial util
26 | let partial = function (fn, ...presetArgs) {
27 | return function partiallyApplied(...laterArgs) {
28 | return fn(...presetArgs, ...laterArgs);
29 | };
30 | }
31 |
32 | // 1. Use a partial to prefil ticker
33 | let getETHPrices = partial(getTickerPrices, 'ETH')
34 |
35 | getETHPrices(['BTC', 'USD', 'AUD', 'EUR']) // { ETH: 1, BTC: 0.07713, USD: 502.87, AUD: 678.08, EUR: 432.63 }
36 |
37 | // Using a partial right util (same as partial but to the right)
38 | function partialRight(fn, ...presetArgs) {
39 | return function partiallyApplied(...laterArgs) {
40 | return fn(...laterArgs, ...presetArgs);
41 | };
42 | }
43 |
44 | // 2. Use a partial Right to prefil currency pairs
45 | let getMajorCurrencies = partialRight(getTickerPrices, ['USD','EUR','AUD'])
46 | getMajorCurrencies('ETH') // { ETH: 1, BTC: 0.07726, USD: 502.87, AUD: 679.22, EUR: 432.7 }
47 |
--------------------------------------------------------------------------------
/redux/counter/modular.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Counter using modular JS
10 |
11 |
12 |
55 |
56 |
--------------------------------------------------------------------------------
/modular/es6.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ES6 example
10 |
11 |
12 |
13 | ES6 Modular JS
14 |
15 |
16 |
17 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/strategy/config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | // Using ES6 classes
4 | // class Config {
5 | // constructor(strategy) {
6 | // this.data = {};
7 | // this.strategy = strategy
8 | // }
9 | // get(prop) {
10 | // return this.data[prop]
11 | // }
12 | // set(prop, value) {
13 | // return this.data[prop] = value
14 | // }
15 | // read(file) {
16 | // console.log(`Deserializing from ${file}`);
17 | // this.data = this.strategy.deserialize(fs.readFileSync(file, 'utf-8'))
18 | // console.log(this.data);
19 | // }
20 | // save(file) {
21 | // console.log(`Serializing to ${file}`);
22 | // fs.writeFileSync(file, this.strategy.serialize(this.data))
23 | // console.log(this.data);
24 | // }
25 | // }
26 |
27 | // Using function returning object
28 | // let Config = function(strategy){
29 | // let data = {}
30 | // return {
31 | // get: prop => data[prop],
32 | // set: (prop, value) => data[prop] = value,
33 | // read: file => {
34 | // console.log(`Deserializing from ${file}`);
35 | // data = strategy.deserialize(fs.readFileSync(file, 'utf-8'))
36 | // console.log(data);
37 | // },
38 | // save: file => {
39 | // console.log(`Serializing to ${file}`);
40 | // fs.writeFileSync(file, strategy.serialize(data))
41 | // console.log(data);
42 | // }
43 | // }
44 | // }
45 |
46 | // Using constructor function
47 | let Config = function(strategy){
48 | this.data = {}
49 | this.strategy = strategy
50 | this.get = (prop) => this.data[prop]
51 | this.set = (prop, value) => this.data[prop] = value
52 | this.read = (file) => {
53 | console.log(`Deserializing from ${file}`);
54 | this.data = this.strategy.deserialize(fs.readFileSync(file, 'utf-8'))
55 | console.log(this.data);
56 | }
57 | this.save = (file) => {
58 | console.log(`Serializing to ${file}`);
59 | fs.writeFileSync(file, this.strategy.serialize(this.data))
60 | console.log(this.data);
61 | }
62 | }
63 |
64 | module.exports = Config;
65 |
--------------------------------------------------------------------------------
/test/chain-of-resp/index.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let {Products, Discount} = require('../../chain-of-resp/index.js')
3 |
4 | describe('#Products', function () {
5 | it('should add and sore prices', () => {
6 | let products = new Products()
7 | products.addProduct(10)
8 | products.addProduct(50)
9 | products.addProduct(100)
10 | products.addProduct(20)
11 | let input = products.products
12 | let actual = [10, 50, 100, 20]
13 | expect(input).to.eql(actual)
14 | })
15 | })
16 | describe('#Discount', function(){
17 | it('calculate 10% discount if > 3 items', () => {
18 | let products = new Products()
19 | products.addProduct(10)
20 | products.addProduct(10)
21 | products.addProduct(4)
22 | products.addProduct(20)
23 | let discount = new Discount()
24 | let input = discount.calculate(products.products)
25 | let actual = '0.10'
26 | expect(input).to.equal(actual)
27 | })
28 | it('calculate 20% discount if total > $100', () => {
29 | let products = new Products()
30 | products.addProduct(120)
31 | let discount = new Discount()
32 | let input = discount.calculate(products.products)
33 | let actual = '0.20'
34 | expect(input).to.equal(actual)
35 | })
36 | it('calculate 30% discount if total > $100 plus > 3 items', () => {
37 | let products = new Products()
38 | products.addProduct(50)
39 | products.addProduct(40)
40 | products.addProduct(5)
41 | products.addProduct(50)
42 | let discount = new Discount()
43 | let input = discount.calculate(products.products)
44 | let actual = '0.30'
45 | expect(input).to.equal(actual)
46 | })
47 | it('calculate 0% discount if total < $100 or < 3 items', () => {
48 | let products = new Products()
49 | products.addProduct(50)
50 | products.addProduct(40)
51 | let discount = new Discount()
52 | let input = discount.calculate(products.products)
53 | let actual = '0.00'
54 | expect(input).to.equal(actual)
55 | })
56 | })
--------------------------------------------------------------------------------
/modular/es5.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ES5 example
9 |
10 |
11 | ES5 Modular JS
12 |
13 |
14 |
15 |
70 |
71 |
--------------------------------------------------------------------------------
/test/chain-of-resp/trafficLights.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let sinon = require('sinon')
3 | let { Timer, Light, Color } = require('../../chain-of-resp/trafficLights.js')
4 |
5 | describe('#TrafficLights', function () {
6 | let NS;
7 | let EW;
8 | beforeEach(function(){
9 | this.clock = sinon.useFakeTimers()
10 | NS = new Light('North South')
11 | EW = new Light('East West')
12 | NS.setNext(EW)
13 | EW.setNext(NS)
14 | // // Comment out, only purpose is to log the seconds passed
15 | // let timer = new Timer()
16 | // timer.init()
17 | })
18 | afterEach(function(){
19 | this.clock.restore()
20 | })
21 | it('should be red for both NS and EW before it runs', function() {
22 | this.clock.tick(0)
23 | expect(NS.state.color).to.equal('red')
24 | expect(EW.state.color).to.equal('red')
25 | })
26 | it('should be green for NS and red EW as soon as it runs', function() {
27 | NS.exec()
28 | this.clock.tick(0)
29 | expect(NS.state.color).to.equal('green')
30 | expect(EW.state.color).to.equal('red')
31 | })
32 | it('should turn NS yellow after 10 seconds and EW should stay red', function() {
33 | NS.exec()
34 | this.clock.tick(10000)
35 | expect(NS.state.color).to.equal('yellow')
36 | expect(EW.state.color).to.equal('red')
37 | })
38 | it('should stay NS yellow after 10 seconds for 5 seconds and EW should stay red', function() {
39 | NS.exec()
40 | this.clock.tick(14000)
41 | expect(NS.state.color).to.equal('yellow')
42 | expect(EW.state.color).to.equal('red')
43 | })
44 | it('should turn NS red after 16 seconds and turn EW green', function() {
45 | NS.exec()
46 | this.clock.tick(16000)
47 | expect(NS.state.color).to.equal('red')
48 | expect(EW.state.color).to.equal('green')
49 | })
50 | it('should turn EW red after 31 seconds and turn NS green', function() {
51 | NS.exec()
52 | this.clock.tick(31000)
53 | expect(NS.state.color).to.equal('green')
54 | expect(EW.state.color).to.equal('red')
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/redux/counter/react-reducer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Counter using React JS
12 |
13 |
14 |
15 |
16 |
17 |
18 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/fp-light/partial/2.js:
--------------------------------------------------------------------------------
1 | let axios = require('axios')
2 |
3 | // Basic wrapper around ajax requestModule
4 | let fetchURL = (requestModule, url) => {
5 | return new Promise((resolve, reject) => {
6 | requestModule.get(url).then(resp => {
7 | resolve(resp.data)
8 | })
9 | .catch(err => reject(err))
10 | })
11 | }
12 |
13 | // Need to inject twice which is bad
14 | fetchURL(axios, 'https://api.coinmarketcap.com/v2/ticker/?limit=10')
15 | .then(resp => {
16 | let data = resp
17 | return data
18 | })
19 |
20 | fetchURL(axios, 'https://api.coinmarketcap.com/v2/ticker/?limit=20')
21 | .then(resp => {
22 | let data = resp
23 | return data
24 | })
25 |
26 |
27 | // Using a partial which allows us to pre-fill arguments for the fn we want to use
28 | let partial = function(fn, ...presetArgs) {
29 | return function partiallyApplied(...laterArgs) {
30 | return fn(...presetArgs, ...laterArgs);
31 | };
32 | }
33 |
34 | // Prefill our fetchURL by injecting with axios dependency
35 | // This is basically dependency injection
36 | let getURL = partial(fetchURL, axios)
37 |
38 | getURL('https://api.coinmarketcap.com/v2/ticker/?limit=10')
39 | .then(resp => {
40 | let data = resp
41 | return data
42 | })
43 |
44 | getURL('https://api.coinmarketcap.com/v2/ticker/?limit=20')
45 | .then(resp => {
46 | let data = resp
47 | return data
48 | })
49 |
50 |
51 | // ES6 has another elegant way to perform dependency injection using default params
52 | // The difference here is we put the external dependency as the final args and reference it when testing
53 | // In production code we leave it off and let it refer to the default param
54 | let fetchURL_ES6 = (url, requestModule = axios) => {
55 | return new Promise((resolve, reject) => {
56 | requestModule.get(url).then(resp => {
57 | resolve(resp.data)
58 | })
59 | .catch(err => reject(err))
60 | })
61 | }
62 |
63 | // I prefer this approach as its cleaner and testable with dependency injection
64 | fetchURL_ES6('https://api.coinmarketcap.com/v2/ticker/?limit=10')
65 | .then(resp => {
66 | let data = resp
67 | return data
68 | })
69 |
--------------------------------------------------------------------------------
/react/tutorial/clock/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React
10 |
11 |
12 |
13 |
14 |
15 |
16 | React
17 |
18 |
19 |
20 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/js-language/constructor_vs_classes/example_4.js:
--------------------------------------------------------------------------------
1 | // # Classes using extends to create sub classes
2 | // Classes can inherit from other classes by using extends, this is the same as using .call(this) with constructor functions to inherit from other factory functions
3 | // NOTE: in general when writing your custom logic it is a bad idea to use inheritence (either class or constructor functions)
4 | // Attempt to use functional composition as it easier to test and reuse
5 | // However; you will notice sub classes a lot in other JS libraries that inherit from master components e.g. React [class Square extends React.Component]
6 |
7 | // 1. Start with a simple person class
8 | class Person {
9 | constructor(name){
10 | this.name = name
11 | }
12 | speak() {
13 | return `My name is ${this.name}`
14 | }
15 | }
16 |
17 | let howie = new Person('howie')
18 | howie.speak() // My name is howie
19 |
20 | // 2. Extend the Person class to a subclass of Worker
21 | class Worker extends Person {
22 | constructor(name, profession) {
23 | // Call super(name) to call on the parent class and instantiate the Person constructor with the name
24 | // Super must be called before this can be used
25 | super(name)
26 | this.profession = profession
27 | }
28 | // This speak method overrides the parent class
29 | speak() {
30 | return `My name is ${this.name} and my profession is ${this.profession}`
31 | }
32 | }
33 |
34 | let bob = new Worker('Bob', 'Builder')
35 | bob.speak() // My name is Bob and my profession is Builder
36 |
37 | // # Constructor function using inheritence with .call(this) to do same thing
38 | let PersonConstructor = function (name) {
39 | this.name = name
40 | this.speak = () => `My name is ${this.name}`
41 | }
42 |
43 | let WorkerConstructor = function (name, profession) {
44 | PersonConstructor.call(this, name)
45 | this.profession = profession
46 | this.speak = () => `My name is ${this.name} and my profession is ${this.profession}`
47 | }
48 |
49 | let hela = new PersonConstructor('hela')
50 | hela.speak() // My name is hela
51 |
52 | let felix = new WorkerConstructor('felix', 'unemployed')
53 | felix.speak() // My name is felix and my profession is unemployed
54 |
--------------------------------------------------------------------------------
/chain-of-resp/trafficLights.js:
--------------------------------------------------------------------------------
1 |
2 | let Light = function (direction, timer) {
3 | // Initial state e.g. NS, EW traffic light
4 | this.state = {
5 | color: 'red',
6 | direction
7 | }
8 |
9 | // Chain-of-resp design pattern. Set next function to be other light
10 | this.next = null
11 | this.setNext = function (fn) {
12 | this.next = fn
13 | }
14 |
15 | // Chain-of-resp, instantiate the colors and set order and time interval they are to be called
16 | // For last color red set the next light that is to be called
17 | this.exec = function () {
18 | let green = new Color(this.state, 'green', 10)
19 | let yellow = new Color(this.state, 'yellow', 5)
20 | let red = new Color(this.state, 'red', 0)
21 | green.setNext(yellow)
22 | yellow.setNext(red)
23 | red.setNext(this.next)
24 | green.exec()
25 | }
26 | }
27 |
28 | let Color = function(light, color, seconds){
29 | // Pass the light state to the color
30 | this.light = light
31 |
32 | // Chain-of-resp design pattern cache next function to be called
33 | this.next = null
34 | this.setNext = function (fn) {
35 | this.next = fn
36 | }
37 |
38 | // Change state color and call next function after a set second interval period
39 | this.exec = function () {
40 | // Call logic here -> change the state color and log the result
41 | this.light.color = color
42 | console.log(this.light.color, this.light.direction)
43 |
44 | // Call next fn after n seconds
45 | setTimeout(() => {
46 | this.next.exec()
47 | }, seconds * 1000)
48 | }
49 | }
50 |
51 | // Simple timer which logs seconds passed. Not really needed for logic
52 | let Timer = function () {
53 | this.seconds = 0
54 | this.init = () => {
55 | setInterval(() => {
56 | this.seconds += 1
57 | console.log(this.seconds)
58 | }, 1000)
59 | }
60 | }
61 |
62 | // // Setup the lights with given direction, set next ones to be called to be each other (infinite loop), then call NS first
63 | // let NS = new Light('North South')
64 | // let EW = new Light('East West')
65 | // NS.setNext(EW)
66 | // EW.setNext(NS)
67 | // NS.exec()
68 |
69 | // let timer = new Timer()
70 | // timer.init()
71 |
72 | module.exports = {Timer, Light, Color}
--------------------------------------------------------------------------------
/modular/es6part2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ES6 example with subclasses
10 |
11 |
12 |
13 | ES6 Modular JS with subclasses
14 |
15 |
16 |
17 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/fp-light/examples/promises.js:
--------------------------------------------------------------------------------
1 | let pipe = require('lodash/fp/pipe');
2 | let _ = require('lodash');
3 | let partial = _.partial;
4 |
5 | // Exercise: transform shopping cart data to return total checkout price after 3% sales disc and 10% GST
6 | // Get data from API endpoint and then return calculation
7 | let data = [
8 | {price: 12.00, quantity: 1, item: 'Tooth Brush'},
9 | {price: 4.00, quantity: 2, item: 'Tooth Paste'},
10 | {price: 15.00, quantity: 2, item: 'Toilet Paper'},
11 | {price: 5.50, quantity: 4, item: 'Soap'},
12 | {price: 6.50, quantity: 1, item: 'Laundry Powder'}
13 | ]
14 |
15 | // Fake promise
16 | let fetchData = () => Promise.resolve(data)
17 |
18 | // 1. Imperative approach
19 | // Advantage easier to write but harder to read and test
20 | let checkOut = function(){
21 | return fetchData().then(data => {
22 | let totalArr = data.map(item => item.price * item.quantity)
23 | let totalDiscArr = totalArr.map(item => item * 0.97)
24 | let totalTaxArr = totalDiscArr.map(item => item * 1.1)
25 | let sumTotal = totalTaxArr.reduce(function (sum, item) {
26 | return sum += item
27 | }, 0)
28 | return sumTotal
29 | })
30 | }
31 |
32 | checkOut() // 83.7595
33 |
34 | // 2. Functional
35 | // More verbose but easier to test and read over time
36 | let map = (fn, arr) => arr.map(fn)
37 | let subTotal = (item) => item.price * item.quantity
38 | let multiply = (x, y) => x * y
39 | let sum = (arr) => arr.reduce(function(tally, el){return tally += el}, 0)
40 |
41 | // Compose a calcTotal function using pipe which expects an array and then executes from left to right
42 | let calcTotal = pipe(
43 | partial(map, subTotal),
44 | partial(map, partial(multiply, 0.97)),
45 | partial(map, partial(multiply, 1.1)),
46 | sum
47 | )
48 |
49 | let checkOutFP = (arr) => fetchData().then(calcTotal)
50 | checkOutFP() // 83.7595
51 |
52 | // Same as above but extra step of spelling out the partial functions
53 | let mapSubTotal = partial(map, subTotal)
54 | let mapDiscount = partial(map, partial(multiply, 0.97))
55 | let mapTax = partial(map, partial(multiply, 1.1))
56 |
57 | let calcTotalVerbose = pipe(
58 | mapSubTotal,
59 | mapDiscount,
60 | mapTax,
61 | sum
62 | )
63 |
64 | let checkOutVerbose = (arr) => fetchData().then(calcTotalVerbose)
65 | checkOutVerbose() // 83.7595
66 |
--------------------------------------------------------------------------------
/fp-light/examples/refactor/google/google.js:
--------------------------------------------------------------------------------
1 | let google = module.exports = {}
2 |
3 | // Dependencies
4 | let config = require('../../config.js');
5 | let dbClient = require('./dbClient.js')
6 | let get = require('lodash/get')
7 | let googleClient = config.googleClient
8 |
9 | google.analyze = (text, client = googleClient) => {
10 | let document = {
11 | content: text,
12 | type: 'PLAIN_TEXT',
13 | }
14 | return client.analyzeSentiment({
15 | document: document
16 | })
17 | }
18 |
19 | /**
20 | * @function {analyzes a single document post title for sentiment and updates document}
21 | * @param {Object} doc {full document object}
22 | * @return {Promise} {dbClient.updateThread put Promise}
23 | */
24 | google.postUpdateSentiment = (doc) => {
25 | let title = get(doc, 'post.title')
26 | let id = doc._id
27 | return new Promise((resolve, reject) => {
28 | if (doc.documentSentiment) {
29 | console.log('Sentiment already exists for: ', title);
30 | resolve('Sentiment already exists')
31 | } else {
32 | google.analyze(title)
33 | .then(result => {
34 | console.log(`New sentiment analyzed: score of ${result[0].documentSentiment.score} for ${title}`);
35 |
36 | resolve(dbClient.updateSentimentScore(id, result[0].documentSentiment.score))
37 | })
38 | }
39 | })
40 | }
41 |
42 | /**
43 | * @function {takes array of documents and analyzes post title for sentiment and updates document}
44 | * @param {Array} array {array of documents}
45 | * @return {Promise} {Promise array of updated docs}
46 | */
47 | google.arrayPostUpdateSentiment = (array) => {
48 | let promiseArr = array.map(doc => {
49 | return google.postUpdateSentiment(doc);
50 | })
51 | return Promise.all(promiseArr)
52 | }
53 |
54 | /**
55 | * @function {queries db based on topicsArr and analyzes sentiment and updates docs}
56 | * @return {Promise} {Promise array of updated docs}
57 | */
58 | google.pollSentiment = () => {
59 | return new Promise(resolve => {
60 | dbClient.getAll().then(payload => {
61 | let array = payload.data
62 | google.arrayPostUpdateSentiment(array).then(results => {
63 | resolve(`No more results, totalResults: ${results.length}`)
64 | })
65 | })
66 | .catch(err => {
67 | console.log(err);
68 | })
69 | })
70 | }
--------------------------------------------------------------------------------
/fp-light/curry/1.js:
--------------------------------------------------------------------------------
1 | // #Currying is a special form of partial application where the arity is reduced to 1, with a chain of successive chained
2 | // function calls, each which takes one argument.Once all arguments have been specified by these
3 | // function calls, the original
4 | // function is executed with all the collected arguments
5 | // You can use chaining with curry
6 |
7 | // Custom fetch
8 | let request = require('request')
9 | let fetch = (url) => {
10 | return new Promise((resolve, reject) => {
11 | request(url, function (err, resp, body) {
12 | if (err) {
13 | reject(err)
14 | }
15 | resolve(JSON.parse(body))
16 | })
17 | })
18 | }
19 |
20 | // arity means how many args it can take
21 | // Curry partial util
22 | let curry = function (fn, arity = fn.length) {
23 | return (function nextCurried(prevArgs) {
24 | return function curried(nextArg) {
25 | var args = [...prevArgs, nextArg];
26 |
27 | if (args.length >= arity) {
28 | return fn(...args);
29 | } else {
30 | return nextCurried(args);
31 | }
32 | };
33 | })([]);
34 | }
35 |
36 | // Example 1: Curry the ticker
37 | let getTickerPrices = (ticker, compareArr) => {
38 | let currencyArr = compareArr.join(',')
39 | let url = `https://min-api.cryptocompare.com/data/price?fsym=${ticker}&tsyms=${ticker},${currencyArr}`
40 | return fetch(url)
41 | }
42 |
43 | curry(getTickerPrices)('ETH')(['USD', 'AUD']) // { ETH: 1, USD: 502.78, AUD: 677.9 }
44 |
45 | // Example 2: Specify the arity required before executing
46 |
47 | // Start with a simple sum function which takes individual args [contrived example]
48 | let sum = function (...nums) {
49 | let numArr = [...nums]
50 | return numArr.reduce((el, tally) => {
51 | return el + tally
52 | },0)
53 | }
54 |
55 | // Creat a curry function which only takes 5 args before executing
56 | let currySumFive = curry(sum, 5)
57 | currySumFive(1)(2)(3)(4) // Returns a function, doesn't meet the 5 arg arity
58 | currySumFive(1)(2)(3)(4)(5) // 15
59 |
60 | // This is how this function would look if we wrote it out by hand (albeit using ES6)
61 | // Eg. a partial function which prefills the final fn until it reaches 5 args
62 | let sumFive = (v1) =>
63 | v2 =>
64 | v3 =>
65 | v4 =>
66 | v5 => sum(v1, v2, v3, v4, v5)
67 |
68 | sumFive(1)(2)(3)(4)(5) // 15
69 |
70 |
--------------------------------------------------------------------------------
/fp-light/ramda/explained.js:
--------------------------------------------------------------------------------
1 | import R from 'ramda';
2 |
3 | let data = [
4 | { university: true, salary: 300, age: 42, gender: 'male', visaId: 'abc2132' },
5 | { university: true, salary: 200, age: 35, gender: 'female' },
6 | { university: true, salary: 80, age: 34, gender: 'male', visaId: '42213asdd' },
7 | { university: true, salary: 300, age: 23, gender: 'female' },
8 | { university: true, salary: 250, age: 52, gender: 'female' },
9 | { university: true, salary: 100, age: 31, gender: 'female' },
10 | { university: true, salary: 200, age: 45, gender: 'male' },
11 | { university: false, salary: 70, age: 18, gender: 'male', visaId: 'asdajsd1223' },
12 | { university: false, salary: 50, age: 21, gender: 'male' },
13 | { university: true, salary: 30, age: 43, gender: 'female' }
14 | ]
15 |
16 |
17 | // 1. Imperative
18 | let getMaleGraduateSalaries1 = (data) => {
19 | let filtered = data.filter(el => el.university && el.gender === 'male' && el.visaId)
20 | let salaries = filtered.map(el => el.salary)
21 | return salaries
22 | }
23 |
24 | let a = getMaleGraduateSalaries1(data)
25 | a // [ 300, 80 ]
26 |
27 | // 2. Declarative using ramda
28 | // Note: may be longer but it is easier to read and reason
29 | let getMaleGraduateSalaries2 = R.pipe(
30 | R.filter(R.propEq('university', true)),
31 | R.filter(R.propEq('gender', 'male')),
32 | R.filter(R.has('visaId')),
33 | R.map(R.prop('salary'))
34 | )
35 |
36 | let b = getMaleGraduateSalaries2(data)
37 | b // [ 300, 80 ]
38 |
39 | // 3. Declarative without ramda, roll our own helpers
40 | let prop = (key) =>
41 | object => object[key]
42 |
43 | let propEq = (key, val) =>
44 | object => object[key] === val ? true : false
45 |
46 | let has = (key) =>
47 | object => key in object
48 |
49 | let filter = (predicate) =>
50 | arr => arr.filter(predicate)
51 |
52 | let map = (fn) =>
53 | arr => arr.map(fn)
54 |
55 | let pipe = function() {
56 | var args = [].slice.call(arguments)
57 | return function(x){
58 | var result = x
59 | args.forEach(f => {
60 | result = f(result)
61 | })
62 | return result
63 | }
64 | }
65 |
66 | let getMaleGraduateSalaries3 = pipe(
67 | filter(propEq('university', true)),
68 | filter(propEq('gender', 'male')),
69 | filter(has('visaId')),
70 | map(prop('salary'))
71 | )
72 |
73 | let c = getMaleGraduateSalaries3(data)
74 | c // [ 300, 80 ]
75 |
--------------------------------------------------------------------------------
/modular/react.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React
10 |
11 |
12 |
13 |
14 |
15 |
16 | React
17 |
18 |
19 |
20 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/react/es5.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | ES5 example
9 |
10 |
11 | ES5 jQuery without React
12 |
13 |
14 |
15 |
74 |
75 |
--------------------------------------------------------------------------------
/react/tutorial/crypto/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | React
10 |
11 |
12 |
13 |
14 |
15 |
16 | React
17 |
18 |
19 |
20 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/state/booking.js:
--------------------------------------------------------------------------------
1 | // Reservation booking system
2 | // Logic
3 | // User can Reserve a spot and can either make payment or cancel their reservation
4 | // Once Paid a user can ask for a refund or Redeem
5 | // Once Redeemed a user can no longer ask for refund
6 |
7 | let Booking = function() {
8 | this.state = new Reserved() // Set the initial state
9 | // Calls the next state and caches its value into this.state
10 | this.nextState = function() {
11 | this.state = this.state.next()
12 | }
13 | this.exitState = function() {
14 | this.state = this.state.exit()
15 | }
16 | }
17 |
18 | // Forward progression
19 | let Reserved = function() {
20 | this.status = 'reservation made'
21 | this.next = function() {
22 | return new Paid()
23 | }
24 | this.exit = function(){
25 | return new ReservedCancel()
26 | }
27 | }
28 |
29 | let Paid = function() {
30 | this.status = 'paid confirmation'
31 | this.next = function() {
32 | return new Redeemed()
33 | }
34 | this.exit = function() {
35 | return new PaidRefund()
36 | }
37 | }
38 |
39 | let Redeemed = function() {
40 | this.status = 'redeemed complete'
41 | this.next = function() {
42 | return this
43 | }
44 | }
45 |
46 | // Exit states
47 | let ReservedCancel = function() {
48 | this.status = 'cancel reservation'
49 | this.next = function () {
50 | return this
51 | }
52 | }
53 |
54 | let PaidRefund = function() {
55 | this.status = 'refund processed'
56 | this.next = function () {
57 | return this
58 | }
59 | }
60 |
61 | // let booking = new Booking()
62 | // console.log(booking.state.status); // reservation made
63 | // booking.nextState()
64 | // console.log(booking.state.status); // paid confirmation
65 | // booking.nextState()
66 | // console.log(booking.state.status); // redeemed complete
67 |
68 | // let booking2 = new Booking()
69 | // console.log(booking2.state.status); // reservation made
70 | // booking2.exitState()
71 | // console.log(booking2.state.status); // cancel reservation
72 | // booking2.nextState()
73 | // console.log(booking2.state.status); // cancel reservation
74 |
75 | // let booking3 = new Booking()
76 | // console.log(booking3.state.status); // reservation made
77 | // booking3.nextState()
78 | // console.log(booking3.state.status); // paid confirmation
79 | // booking3.exitState()
80 | // console.log(booking3.state.status); // refund processed
81 | // booking3.nextState()
82 | // console.log(booking3.state.status); // refund processed
83 |
84 | module.exports = Booking
--------------------------------------------------------------------------------
/redux/counter/modular-reducer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Counter using modular JS
12 |
13 |
14 |
15 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/redux/counter/modular-redux.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Counter using modular JS and Redux
12 |
13 |
14 |
15 |
16 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/fp-light/composition/4.js:
--------------------------------------------------------------------------------
1 | // Expanding on 3.js we will write a utility to compose 2 or more functions
2 | let compose = function(...fns) {
3 | return function composed(result) {
4 | // copy the array of functions
5 | var list = [...fns];
6 |
7 | while (list.length > 0) {
8 | // take the last function off the end of the list
9 | // and execute it
10 | result = list.pop()(result);
11 | }
12 |
13 | return result;
14 | };
15 | }
16 |
17 | // Using example from 2.js to compose more than 2 functions
18 | let data = [
19 | {success: true, seats: 100},
20 | {success: false, seats: 30},
21 | {success: false, seats: 20},
22 | {success: false, seats: 30},
23 | {success: true, seats: 80},
24 | {success: false, seats: 20},
25 | {success: true, seats: 10},
26 | {success: false, seats: 30}
27 | ]
28 |
29 | // We will break this task into three smaller functions
30 | let filterSuccess = (arr, boolean = true) => {
31 | return arr.filter( el => boolean ? el.success : !el.success)
32 | }
33 |
34 | let getProperty = (arr, prop) => {
35 | return arr.map( el => el[prop])
36 | }
37 |
38 | let sum = (arr) => {
39 | return arr.reduce((tally, el) => {
40 | return tally + el
41 | }, 0)
42 | }
43 |
44 | // 1. We will compose the three functions above to create a single function
45 | // BONUS: we will be using our partialRight function to pre-fill our getProperty and filterSuccess functions
46 | let totalNonSuccess = compose(
47 | sum,
48 | partialRight(getProperty, 'seats'),
49 | partialRight(filterSuccess, false)
50 | )
51 |
52 | let total = totalNonSuccess(data)
53 | total
54 |
55 | // Under the hood
56 | let totalNonSuccess2 = (arr) => {
57 | return sum(getProperty(filterSuccess(arr, false), 'seats'))
58 | }
59 | let total2 = totalNonSuccess2(data)
60 | total2 //130
61 |
62 |
63 | // Using a partial right util (same as partial but to the right)
64 | function partialRight(fn, ...presetArgs) {
65 | return function partiallyApplied(...laterArgs) {
66 | return fn(...laterArgs, ...presetArgs);
67 | };
68 | }
69 |
70 |
71 | // 2. Doing same as one but using function instead of partial, the benefit of using partials is you can do it inline and compose
72 | // Optional to you what is easier to read.
73 | // I see the real benefit of using partials here because it shows true lego blocks and less room for error or wrapping
74 | let getSeats = (arr) => getProperty(arr, 'seats')
75 | let filterNonSuccess = (arr) => filterSuccess(arr, false)
76 | let totalNonSuccess3 = compose(
77 | sum,
78 | getSeats,
79 | filterNonSuccess
80 | )
81 | let total3 = totalNonSuccess3(data)
82 | total3 // 130
--------------------------------------------------------------------------------
/fp-light/composition/7.js:
--------------------------------------------------------------------------------
1 | let pipe = require('lodash/fp/pipe');
2 | let _ = require('lodash');
3 |
4 | // Task 1: sum all even numbers and then square it
5 | let data = [1, 2, 3, 4, 5, 10, 11, 12]
6 |
7 | // Imperative approach
8 | let sumEvenSquared = function(arr) {
9 | let evenArr = arr.filter(el => el % 2 === 0)
10 | let sumEvens = evenArr.reduce(function(tally, num){
11 | return tally += num
12 | }, 0)
13 | return sumEvens * sumEvens
14 | }
15 | sumEvenSquared(data) // 784
16 |
17 | // Functional using pipe (reverse of compose)
18 | let isEven = (arr) => arr.filter(el => el % 2 === 0)
19 | let square = (num) => num * num
20 | let sum = (arr) => arr.reduce(function(tally, el){return tally += el, 0})
21 |
22 | let fpSumEvenSquared = pipe(
23 | isEven,
24 | sum,
25 | square
26 | )
27 | fpSumEvenSquared(data) // 784
28 |
29 |
30 | // Task 2: zapier like task. Take payload and change into new format
31 | let payload = {
32 | code: 'AU',
33 | filename: 'Test_File_Name.pdf'
34 | }
35 | // let output = {
36 | // country: 'Australia',
37 | // link: 'https://www.internal.com/Test_File_Name.pdf'
38 | // }
39 |
40 | // Imperative approach
41 | let zapier = (obj) => {
42 | let {code, filename} = obj
43 | let link = `https://www.internal.com/${filename}`
44 | let country;
45 | if (code === 'AU') {
46 | country = 'Australia'
47 | }
48 | if (code === 'US') {
49 | country = 'United States'
50 | }
51 | if (code === 'UK') {
52 | country = 'United Kingdom'
53 | }
54 | return {
55 | country,
56 | link
57 | }
58 | }
59 |
60 | zapier(payload)
61 | // {
62 | // country: 'Australia',
63 | // link: 'https://www.internal.com/Test_File_Name.pdf'
64 | // }
65 |
66 |
67 | // Functional approach
68 | let getFile = _.partialRight(_.get,'filename')
69 | let returnLink = filename => `https://www.internal.com/${filename}`
70 |
71 | let transformLink = obj => {
72 | return Object.assign({...obj}, {
73 | link: pipe(getFile, returnLink)(obj)
74 | })
75 | }
76 |
77 | const COUNTRY_CODES = {
78 | 'AU': 'Australia',
79 | 'US': 'United States',
80 | 'UK': 'United Kingdom'
81 | }
82 |
83 | let transformCountry = obj => {
84 | return Object.assign({...obj}, {
85 | country: COUNTRY_CODES[obj.code]
86 | })
87 | }
88 |
89 | let fpZapier = pipe(
90 | transformLink,
91 | transformCountry,
92 | _.partialRight(_.pick, ['country', 'link'])
93 | )
94 |
95 | fpZapier(payload)
96 | // {
97 | // country: 'Australia',
98 | // link: 'https://www.internal.com/Test_File_Name.pdf'
99 | // }
100 |
--------------------------------------------------------------------------------
/fp-light/merge/1.js:
--------------------------------------------------------------------------------
1 | // ## MERGE: Calls a promise and returns new Object combining promise result and original arg
2 | // Helpful when passing results from previous calls down the line
3 | // Think of a tumbleweed or snowball effect down a zapier pipe
4 |
5 | let identity = o => o // default helper which returns the arg
6 |
7 | const merge = (promise, inner = identity, outer = identity) => o => {
8 | return promise(inner(o)) // calls promise with the relevant property from the objects arg (inner)
9 | .then(outer) // returns the property from the outer arg
10 | .then(result => Object.assign({}, result, o)) // returns a new object combining original object arg and result of promise
11 | }
12 |
13 | // Used from before in partial/1.js
14 | let sideEffect = fn => a => {
15 | fn(a) // process side-effects with arg
16 | return a // pass the arg further
17 | }
18 |
19 | // Used from before in partial/1.js
20 | let fetchAll = () => Promise.resolve({
21 | name: 'Howie',
22 | looks: 'handsome',
23 | id: '123'
24 | })
25 | let postAPI = (id) => Promise.resolve({
26 | status: 'ok',
27 | id: 'secret'
28 | })
29 | let fetchAge = (id) => Promise.resolve({
30 | age: 18,
31 | id: '123'
32 | })
33 |
34 | // 1. Imperative method: Before using merge helper
35 | // Similar to partial/1.js except now we want to pass on existing object to each subsequent call (like a snowball)
36 | fetchAll()
37 | .then(result => {
38 | // There are 2 side effects here but its not clear
39 | console.log(`Name returned: ${result.name}`)
40 | return postAPI(result.id)
41 | .then(resp => {
42 | console.log(resp);
43 | return Object.assign({}, resp, result)
44 | })
45 | })
46 | .then(result => {
47 | let id = result.id
48 | return fetchAge(id).then(resp => {
49 | return Object.assign({}, result, resp)
50 | })
51 | })
52 | .then(result => {
53 | console.log(`Age is: ${result.age}, name is ${result.name} and looks ${result.looks}`);
54 | // Age is: 18, name is Howie and looks handsome
55 | })
56 |
57 | // 2. Declarative method: Using merge helper
58 | // Makes it clearer when we say merge we are snowballing a promise return with the previous object
59 | // Using sideffects here as well makes it clear what we are doing
60 |
61 | fetchAll()
62 | .then(sideEffect(o => console.log(`Name returned: ${o.name}`)))
63 | .then(merge(postAPI, o => o.id))
64 | .then(sideEffect(({status, id}) => console.log({status, id})))
65 | .then(merge(fetchAge))
66 | .then(result => {
67 | console.log(`Age is: ${result.age}, name is ${result.name} and looks ${result.looks}`)
68 | // Age is: 18, name is Howie and looks handsome
69 | })
70 |
--------------------------------------------------------------------------------
/fp-light/playground/1.js:
--------------------------------------------------------------------------------
1 | // Exercise
2 | // Calculate total checkout price for user and trigger payment API
3 | // Reject checkout if there are items that are age restricted
4 |
5 | // Helper dependeicies
6 | let _ = require('lodash')
7 | let R = require('ramda')
8 | let pipe = require('lodash/fp/pipe')
9 |
10 |
11 | let identity = o => o // default helper which returns the arg
12 |
13 | const mergePromise = (promise, inner = identity, outer = identity) => o => {
14 | return promise(inner(o)) // calls promise with the relevant property from the objects arg (inner)
15 | .then(outer) // returns the property from the outer arg
16 | .then(result => Object.assign({}, result, o)) // returns a new object combining original object arg and result of promise
17 | }
18 |
19 | const merge = (fn, inner = identity) => o => {
20 | let result = fn(inner(o))
21 | return Object.assign({}, result, o)
22 | }
23 |
24 | let sideEffect = fn => a => {
25 | fn(a) // process side-effects with arg
26 | return a // pass the arg further
27 | }
28 |
29 | const rejectIfTrue = (fn, rejectValue) => d => {
30 | return fn(d) ? Promise.reject(rejectValue) : d
31 | }
32 |
33 | // Used from before in partial/1.js
34 | let fetchUser = (name) => Promise.resolve({
35 | name: 'Howie',
36 | looks: 'handsome',
37 | id: '123',
38 | checkout: [
39 | {item: 'beer', price: 5.00, qty: 6, status: 'Age Restricted'},
40 | {item: 'whiskey', price: 5.00, qty: 3, status: 'Age Restricted'},
41 | {item: 'chips', price: 5.00, qty: 2}
42 | ]
43 | })
44 | let postAPI = (id, total) => Promise.resolve({
45 | status: 'ok',
46 | id,
47 | total
48 | })
49 | let fetchAge = (id) => Promise.resolve({
50 | age: 19,
51 | id: '123'
52 | })
53 |
54 | // Exercise
55 | // Calculate total checkout price for user and trigger payment API
56 | // Reject checkout if there are items that are age restricted
57 |
58 | let someRestricted = _.partialRight(_.some, {
59 | status: 'Age Restricted'
60 | })
61 | let underage = _.partialRight(_.lte, 18)
62 |
63 | let failCheckout = ({checkout, age}) => {
64 | return someRestricted(checkout) && underage(age) ? true : false
65 | }
66 |
67 | let checkoutTotal = pipe(
68 | _.partialRight(_.map, o => o.qty * o.price),
69 | _.sum
70 | )
71 |
72 | let addTotal = pipe(
73 | merge(arr => ({total: checkoutTotal(arr)}), o => o.checkout)
74 | )
75 |
76 | fetchUser('howie')
77 | .then(sideEffect(o => console.log(`Fetched user id: ${o.id}`)))
78 | .then(mergePromise(fetchAge, o => o.id))
79 | .then(rejectIfTrue(failCheckout, 'Fail Checkout'))
80 | .then(merge(arr => ({total: checkoutTotal(arr)}), o => o.checkout))
81 | .then(o => mergePromise(_.partial(postAPI, o.id), o => o.total)(o))
82 | .then(sideEffect(o => console.log(`${o.name} checkout completed for $${o.total}`)))
83 | .catch(console.error)
84 |
85 |
--------------------------------------------------------------------------------
/pubsub/index.js:
--------------------------------------------------------------------------------
1 | // PubSub pattern, single function that acts as a central control tower for events
2 | // In this example we have an email price alert system for products
3 | // The emailAlert subscribes to a product and passes it a function it wants executed when the product is emitted
4 | // The Product simply decides when to publish the event name and passes it the product object
5 | // The EventEmitter's job is to listen for and execute functions based on a unique eventName both pub and sub agreed to
6 |
7 |
8 | let EventEmitter = function(){
9 | this.events = {}
10 |
11 | this.on = function (eventName, fn) {
12 | // If event does not exist create a new property with the eventName and an empty array
13 | this.events[eventName] = this.events[eventName] || [];
14 | // If event does not exist push the function into the array. An event can have multple listeners
15 | this.events[eventName].push(fn);
16 | }
17 |
18 | this.emit = function (eventName, param) {
19 | // If eventName exists trigger all functions within the array with the data passed in
20 | if (this.events[eventName]) {
21 | this.events[eventName].forEach(function (fn) {
22 | fn(param);
23 | });
24 | }
25 | }
26 | }
27 |
28 | // Create a new instance
29 | let events = new EventEmitter()
30 |
31 | let Product = function(name, price){
32 | this.product = {
33 | name,
34 | price
35 | }
36 | this.setPrice = (num) => {
37 | this.product.price = num
38 | events.emit(this.product.name, this.product)
39 | }
40 | }
41 |
42 | let EmailAlert = function(email){
43 | this.email = email
44 | this.msg = null
45 | this.priceAlert = null
46 |
47 | this.setAlert = (price, product) => {
48 | this.priceAlert = price
49 | events.on(product, this.calculate)
50 | }
51 | this.calculate = (product) => {
52 | let {price, name} = product
53 | if (price < this.priceAlert) {
54 | this.msg = `Email to ${this.email}: ${name} price below ${this.priceAlert} now at ${price}`
55 | }
56 | }
57 | }
58 |
59 | module.exports = {Product, EmailAlert}
60 |
61 | // let book = new Product('book', 30)
62 | // let johnAlert = new EmailAlert('john@email.com')
63 | // let billAlert = new EmailAlert('bill@email.com')
64 | // let kateAlert = new EmailAlert('kate@email.com')
65 | // let mindyAlert = new EmailAlert('mindy@email.com')
66 | // johnAlert.setAlert(20, 'book')
67 | // billAlert.setAlert(25, 'book')
68 | // kateAlert.setAlert(17, 'book')
69 | // mindyAlert.setAlert(5, 'book')
70 |
71 | // // Trigger event
72 | // book.setPrice(15)
73 |
74 | // let input = [
75 | // johnAlert.msg,
76 | // billAlert.msg,
77 | // kateAlert.msg,
78 | // mindyAlert.msg
79 | // ]
80 | // let actual = [
81 | // 'Email to john@email.com: book price below 20 now at 15',
82 | // 'Email to bill@email.com: book price below 25 now at 15',
83 | // 'Email to kate@email.com: book price below 17 now at 15',
84 | // null
85 | // ]
86 | // expect(input).to.eql(actual)
87 |
--------------------------------------------------------------------------------
/chain-of-resp/getUser.js:
--------------------------------------------------------------------------------
1 | // Chain of resp boilerplate
2 | let Foo = function() {
3 | this.next = null
4 | this.setNext = (fn) => this.next = fn
5 | this.exec = () => {
6 | return 'I do magic foo stuff here' + this.next.exec()
7 | }
8 | }
9 |
10 | // Example getUser background
11 | let howie = {
12 | profile: {
13 | position: 'Senior Director',
14 | experience: 3
15 | },
16 | company: {
17 | name: 'ACME Inc',
18 | staff: 300
19 | },
20 | account: {
21 | type: 'premium',
22 | active: true,
23 | users: 1
24 | }
25 | }
26 |
27 | let fetchUser = () => {
28 | return Promise.resolve(howie)
29 | }
30 |
31 | let CheckProfile = function() {
32 | this.next = null
33 | this.setNext = (fn) => this.next = fn
34 | this.exec = (user) => {
35 | let {position, experience} = user.profile
36 | let result = {
37 | checkProfile: {
38 | pass: false,
39 | position,
40 | experience
41 | }
42 | }
43 |
44 | if (experience > 5) {
45 | result.checkProfile.pass = true
46 | }
47 | return Object.assign(result, this.next.exec(user))
48 | }
49 | }
50 |
51 | let CheckCompany = function() {
52 | this.next = null
53 | this.setNext = (fn) => this.next = fn
54 | this.exec = (user) => {
55 | let {name, staff} = user.company
56 | let result = {
57 | checkCompany: {
58 | pass: false,
59 | name,
60 | staff
61 | }
62 | }
63 |
64 | if (staff > 100) {
65 | result.checkCompany.pass = true
66 | }
67 | return Object.assign(result, this.next.exec(user))
68 | }
69 | }
70 |
71 | let CheckAccount = function() {
72 | this.next = null
73 | this.setNext = (fn) => this.next = fn
74 | this.exec = (user) => {
75 | let {type, active, users} = user.account
76 | let result = {
77 | checkAccount: {
78 | pass: false,
79 | type,
80 | active,
81 | users
82 | }
83 | }
84 |
85 | if (active) {
86 | result.checkAccount.pass = true
87 | }
88 | return Object.assign(result, this.next.exec(user))
89 | }
90 | }
91 |
92 | let None = function (){
93 | this.exec = function(){
94 | return
95 | }
96 | }
97 |
98 | let checkProfile = new CheckProfile()
99 | let checkCompany = new CheckCompany()
100 | let checkAccount = new CheckAccount()
101 | let none = new None()
102 | checkProfile.setNext(checkCompany)
103 | checkCompany.setNext(checkAccount)
104 | checkAccount.setNext(none)
105 |
106 |
107 | fetchUser()
108 | .then(user => {
109 | let result = checkProfile.exec(user)
110 | return result
111 | // {
112 | // checkProfile: { pass: false, position: 'Senior Director', experience: 3 },
113 | // checkCompany: { pass: true, name: 'ACME Inc', staff: 300 },
114 | // checkAccount: { pass: true, type: 'premium', active: true, users: 1 }
115 | // }
116 | })
117 |
118 |
--------------------------------------------------------------------------------
/fp-light/curry/2.js:
--------------------------------------------------------------------------------
1 | // Using curry and partial to pre-fill a unit converter function
2 | // The custom function takes 4 args, the first 3 are used to calculate the unit conversion, measurement and any offsets required
3 | // The final input arg takes the first three pre-filled args to convert the metric
4 | // We demonstrate how we can use curry and regular partial left to pre-fill the args to create custom unit converters
5 | // We will create a milesToKm, poundsToKg and farenheitToCelsius converter
6 |
7 | // 1. Curry: takes the arguments one by one to pre-fill in a chain
8 | // 2. Partial: takes all the pre-filled arguments at once
9 | // 3. Simple function: wrap the converter in a simple function
10 |
11 | // Conclusion: I prefer using option 3, it's simpler and easier to understand. But it is good to understand how partial and curry can help
12 |
13 | // Custom unit coverter
14 | let converter = function (toUnit, factor, offset, input) {
15 | let result = ((offset + input) * factor).toFixed(2)
16 | return `${result} ${toUnit}`
17 | }
18 |
19 | // Curry partial util
20 | let curry = function (fn, arity = fn.length) {
21 | return (function nextCurried(prevArgs) {
22 | return function curried(nextArg) {
23 | var args = [...prevArgs, nextArg];
24 |
25 | if (args.length >= arity) {
26 | return fn(...args);
27 | } else {
28 | return nextCurried(args);
29 | }
30 | };
31 | })([]);
32 | }
33 |
34 | // Using curry to prefill args
35 | let milesToKm = curry(converter)('km')(1.60936)(0)
36 | let poundsToKg = curry(converter)('kg')(0.45460)(0)
37 | let farenheitToCelsius = curry(converter)('degrees C')(0.5556)(-32)
38 |
39 | milesToKm(10); // returns "16.09 km"
40 | poundsToKg(2.5); // returns "1.14 kg"
41 | farenheitToCelsius(98); // returns "36.67 degrees C"
42 |
43 |
44 | // Using a partial util
45 | let partial = function (fn, ...presetArgs) {
46 | return function partiallyApplied(...laterArgs) {
47 | return fn(...presetArgs, ...laterArgs);
48 | };
49 | }
50 |
51 | // Same thing but using a traditional partial, just for the sake of it
52 | let milesToKm2 = partial(converter, 'km', 1.60936, 0)
53 | let poundsToKg2 = partial(converter, 'kg', 0.45460, 0)
54 | let farenheitToCelsius2 = partial(converter, 'degrees C', 0.5556, -32)
55 |
56 | milesToKm2(10); // returns "16.09 km"
57 | poundsToKg2(2.5); // returns "1.14 kg"
58 | farenheitToCelsius2(98); // returns "36.67 degrees C"
59 |
60 | // Remember: Use the right tool for the right job.
61 | // Wrapping the convert in a simple fn delivers the same outcome as using curry
62 | // Personally I find this easier to understand, test and reason about
63 | // It is clear how many args the converter function takes
64 | let milesToKm3 = input => converter('km', 1.60936, 0, input)
65 | let poundsToKg3 = input => converter('kg', 0.45460, 0, input)
66 | let farenheitToCelsius3 = input => converter('degrees C', 0.5556, -32, input)
67 |
68 | milesToKm3(10); // returns "16.09 km"
69 | poundsToKg3(2.5); // returns "1.14 kg"
70 | farenheitToCelsius3(98); // returns "36.67 degrees C"
71 |
72 |
73 |
--------------------------------------------------------------------------------
/fp-light/partial/1.js:
--------------------------------------------------------------------------------
1 | let axios = require('axios')
2 |
3 | // Original
4 | let fetchAll = () => {
5 | return axios.get('https://api.coinmarketcap.com/v2/ticker/?limit=10')
6 | }
7 |
8 | let fetchGlobal = () => {
9 | return axios.get(`https://api.coinmarketcap.com/v2/global`)
10 | }
11 |
12 | let fetchTicker = (id) => {
13 | return axios.get(`https://api.coinmarketcap.com/v2/ticker/${id}`)
14 | }
15 |
16 |
17 | // Using partial
18 | let makeGetPartial = function(api) {
19 | return function endpointPartial(endpoint) {
20 | return function finalPartial(param) {
21 | let baseURL = `${api}/${endpoint}`
22 | let url = param ? `${baseURL}${param}` : baseURL
23 | return axios.get(url)
24 | }
25 | }
26 | }
27 |
28 | var makeCoinAPI = makeGetPartial('https://api.coinmarketcap.com/v2')
29 | var getAll = makeCoinAPI('ticker/?limit=10')
30 | var getGlobal = makeCoinAPI('global')
31 | var getTicker = makeCoinAPI('ticker/')
32 |
33 | getAll().then(resp => {
34 | let data = resp.data.data
35 | return data
36 | })
37 |
38 | getGlobal().then(resp => {
39 | let data = resp.data.data
40 | return data
41 | })
42 |
43 | getTicker(3).then(resp => {
44 | let data = resp.data.data
45 | return data
46 | })
47 |
48 | var makeCryptoAPI = makeGetPartial('https://min-api.cryptocompare.com/data')
49 | var compareETH = makeCryptoAPI('price?fsym=ETH&tsyms=')
50 | var compareLTC = makeCryptoAPI('price?fsym=LTC&tsyms=')
51 | var compareETHFull = makeCryptoAPI('pricemultifull?fsyms=ETH&tsyms=')
52 |
53 | compareETH('BTC,USD,EUR,AUD').then(resp => {
54 | let data = resp.data
55 | return data
56 | })
57 |
58 | compareLTC('BTC,USD,EUR,AUD').then(resp => {
59 | let data = resp.data
60 | return data
61 | })
62 |
63 | compareETHFull('BTC,USD,EUR,AUD').then(resp => {
64 | let data = resp.data
65 | return data
66 | })
67 |
68 | let compareETHFullClean = function(tickerArr) {
69 | let pairs = tickerArr.join(',')
70 | return new Promise (resolve => {
71 | compareETHFull(pairs).then(resp => {
72 | let result = resp.data.DISPLAY.ETH
73 | resolve(result)
74 | })
75 | })
76 | }
77 |
78 | compareETHFullClean(['BTC','USD','EUR','AUD']).then(resp => {
79 | let data = resp
80 | return data
81 | })
82 |
83 | // Or same thing as above but using constructor function
84 | let MakeGetPartial = function (api) {
85 | this.domain = api
86 | this.buildEndpoint = (endpoint) => {
87 | return param => {
88 | let baseURL = `${this.domain}/${endpoint}`
89 | let url = param ? `${baseURL}${param}` : baseURL
90 | return axios.get(url)
91 | }
92 | }
93 | }
94 |
95 | var coinAPI = new MakeGetPartial('https://api.coinmarketcap.com/v2')
96 | var getCoins = coinAPI.buildEndpoint('ticker/?limit=10')
97 | var getCoinTicker = coinAPI.buildEndpoint('ticker/')
98 |
99 | getCoins().then(resp => {
100 | let data = resp.data
101 | return data
102 | })
103 |
104 | getCoinTicker(1).then(resp => {
105 | let data = resp.data
106 | return data
107 | })
108 |
--------------------------------------------------------------------------------
/chain-of-resp/index.js:
--------------------------------------------------------------------------------
1 | // Delegate responsibility to next function down the line
2 | // Chain of responsiblity is useful when there are a cumulative series of methods
3 | // In this example we have a discount calculator which is cumulative based on various
4 | // conditions the products basket satisfies e.g. > 3 items add 10% discount, > $100 spend add 20% discount
5 | // To calculate discount we first check if it satisfies number discount, then add that result to the price discount
6 | // before finalling adding a no discount end condition. If number and price discount aren't satisfied then we return 0 discount (the end condition)
7 | // Note: see below for comparison using imperative programming
8 | // Advantage of chain-of-resp pattern is we can choose which types of discounts we want to apply
9 | // we could easily add more discount methods
10 |
11 | // Setup a simple products array which we will use to calc discounts
12 | let Products = function() {
13 | this.products = []
14 | this.addProduct = (product) => {
15 | this.products.push(product)
16 | }
17 | }
18 |
19 | // Entry point function
20 | let Discount = function() {
21 | this.calculate = (products) => {
22 | // Instantiate each of the discount calculators
23 | let ndiscount = new NumberDiscount()
24 | let pdiscount = new PriceDiscount()
25 | let none = new NoDiscount()
26 |
27 | ndiscount.setNext(pdiscount) // Add price discount to number discount
28 | pdiscount.setNext(none) // Add no discount to price discount
29 | return ndiscount.exec(products).toFixed(2) // Execute and round to 2 dp
30 | }
31 | }
32 |
33 | let NumberDiscount = function() {
34 | // PATTERN CODE
35 | // Cache the next instantiated function into next property
36 | this.next = null
37 | this.setNext = function(fn) {
38 | this.next = fn
39 | }
40 |
41 | // Execute by calcuating own result and then append results by calling next
42 | this.exec = function(products){
43 | let result = null
44 | if (products.length > 3) {
45 | result = 0.10
46 | }
47 | return result + this.next.exec(products)
48 | }
49 | }
50 |
51 | let PriceDiscount = function() {
52 | this.next = null
53 | this.setNext = function (fn) {
54 | this.next = fn
55 | }
56 | this.exec = function(products) {
57 | let total = products.reduce((el, tally) => {
58 | return el + tally
59 | }, 0)
60 | let result = null
61 | if (total > 100) {
62 | result = 0.20
63 | }
64 | return result + this.next.exec(products)
65 | }
66 | }
67 |
68 | let NoDiscount = function() {
69 | // End of the line returns 0
70 | this.exec = function(){
71 | return 0
72 | }
73 | }
74 |
75 | // // Imperative method as a comparison
76 | // // Note the code here is not a lot but it does not scale as complexity grows
77 | // let Discount = function() {
78 | // this.calculate = function(products) {
79 | // // Number discount
80 | // let ndiscount = null
81 | // if (products.length > 3) {
82 | // ndiscount = 0.10
83 | // }
84 | // // Price discount
85 | // let pdiscount = null
86 | // let total = products.reduce((el, tally) => {
87 | // return el + tally
88 | // }, 0)
89 | // if(total > 100) {
90 | // pdiscount = 0.20
91 | // }
92 | // // Result
93 | // let result = ndiscount + pdiscount
94 | // return result.toFixed(2)
95 | // }
96 | // }
97 |
98 | module.exports = {Products, Discount}
--------------------------------------------------------------------------------
/redux/counter/modular-reducer2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Counter using modular reducer JS
13 |
14 |
15 |
16 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/checkOut/util.spec.js:
--------------------------------------------------------------------------------
1 | let expect = require('chai').expect
2 | let util = require('./util.js')
3 |
4 |
5 | describe('#util', function () {
6 | describe('#calcSubTotal', () => {
7 | it('should calculate the subTotal amount', () => {
8 | let lineItems = [
9 | {product: 'book', price: 1.50, quantity: 2},
10 | {product: 'shirt', price: 21.50, quantity: 1},
11 | {product: 'underwear', price: 5.50, quantity: 3},
12 | {product: 'shoes', price: 55.50, quantity: 1}
13 | ]
14 |
15 | let input = util.calcSubTotal(lineItems)
16 | let actual = 96.5
17 | expect(input).to.eql(actual)
18 | })
19 | })
20 |
21 | describe('#calcSubTotalWithDiscount', () => {
22 | it('should calculate the subTotal amount adjusting for discount', () => {
23 | let lineItems = [{
24 | product: 'book',
25 | price: 1.50,
26 | quantity: 2,
27 | discount: 0.05
28 | },
29 | {
30 | product: 'shirt',
31 | price: 21.50,
32 | quantity: 1
33 | },
34 | {
35 | product: 'underwear',
36 | price: 5.50,
37 | quantity: 3
38 | },
39 | {
40 | product: 'shoes',
41 | price: 55.50,
42 | quantity: 1,
43 | discount: 0.1
44 | }
45 | ]
46 |
47 | let input = util.calcSubTotalWithDiscount(lineItems)
48 | let actual = 90.80
49 | expect(input).to.eql(actual)
50 | })
51 | })
52 |
53 | describe('#checkTax', () => {
54 | it('should return 10% for AUD', () => {
55 | let input = util.checkTax('AUD')
56 | let actual = 0.1
57 | expect(input).to.equal(actual)
58 | })
59 | it('should return 5% for EUR', () => {
60 | let input = util.checkTax('EUR')
61 | let actual = 0.05
62 | expect(input).to.equal(actual)
63 | })
64 | it('should return 0% for unknown currency', () => {
65 | let input = util.checkTax('MYR')
66 | let actual = 0
67 | expect(input).to.equal(actual)
68 | })
69 | it('should return 0% for invalid values', () => {
70 | let invalidArr = [null, undefined, 1, {}, []]
71 | invalidArr.forEach(invalid => {
72 | let input = util.checkTax(invalid)
73 | let actual = 0
74 | expect(input).to.equal(actual)
75 | })
76 | })
77 |
78 | })
79 |
80 | describe('#checkShipping', () => {
81 | it('should return correct fare by zone', () => {
82 | let shipping = {
83 | A: 5,
84 | B: 10,
85 | C: 15,
86 | D: 25
87 | }
88 | let zones = Object.keys(shipping)
89 | zones.forEach(zone => {
90 | let input = util.checkShipping(zone)
91 | let actual = shipping[zone]
92 | expect(input).to.equal(actual)
93 | })
94 | })
95 | it('should fallback to $5', () => {
96 | let invalidArr = [null, undefined, 1, 'Q', [], {}]
97 | invalidArr.forEach(invalid => {
98 | let input = util.checkShipping(invalid)
99 | let actual = 5
100 | expect(input).to.equal(actual)
101 | })
102 | })
103 |
104 | })
105 |
106 | describe('#toCurrency', () => {
107 | it('should convert to currency at 2 dp', () => {
108 | let input = util.toCurrency(2.1)
109 | let actual = '$2.10'
110 | expect(input).to.equal(actual)
111 | })
112 | })
113 |
114 | describe('#toPercent', () => {
115 | it('should convert to percentage at 2 dp', () => {
116 | let input = util.toPercent(0.05)
117 | let actual = '5.00%'
118 | expect(input).to.equal(actual)
119 | })
120 | })
121 |
122 | })
--------------------------------------------------------------------------------
/fp-light/composition/5.js:
--------------------------------------------------------------------------------
1 | // Another compose example combined with partials
2 |
3 | // Example dataset problem
4 | // Find the total visits and signups to all pages that contain the url matching word magic
5 | // Calculate the average signup converison rate for the above
6 | let data = [
7 | {
8 | site: 'abc.com/magic/apple',
9 | visits: 1000,
10 | signups: 30
11 | },
12 | {
13 | site: 'abc.com/magic/banana',
14 | visits: 3000,
15 | signups: 40
16 | },
17 | {
18 | site: 'abc.com/',
19 | visits: 10000,
20 | signups: 500
21 | },
22 | {
23 | site: 'abc.com/about',
24 | visits: 20000,
25 | signups: 30
26 | },
27 | {
28 | site: 'abc.com/contact',
29 | visits: 20000,
30 | signups: 100
31 | },
32 | {
33 | site: 'abc.com/magic/pear',
34 | visits: 500,
35 | signups: 20
36 | },
37 | {
38 | site: 'abc.com/magic/pineapple',
39 | visits: 1500,
40 | signups: 200
41 | }
42 | ]
43 |
44 | // Break the problem down into single utility functions first. We use the following 3 utils
45 | // Matches a string against a regex word. Returns boolean
46 | let checkRe = (text, word) => {
47 | return new RegExp(word).test(text)
48 | }
49 |
50 | // Filters an array of objects based on a property and boolean function
51 | let filter = (arr, prop, fn) => {
52 | return arr.filter(el => fn(el[prop]))
53 | }
54 |
55 | // Sums total values of an object property in an array
56 | let sum = (arr, prop) => {
57 | return arr.reduce((tally, el) => {
58 | return el[prop] + tally
59 | }, 0)
60 | }
61 |
62 | // Create a new funciton which utilizes the util functions above to compose a larger function
63 | // Use our compose helper with partial to setout the order and method we want the data to be executed
64 | // E.g. 1) we first want to filter the data by the site property for all regex patterns matching the given urlMatch param
65 | // 2) Then we sum the result of filtered array objects based on the given prop
66 | // By using compose this then returns a function waiting for the data array to begin its work
67 | let totalURLByPropCalculator = (urlMatch, prop) => {
68 | return compose(
69 | partialRight(sum, prop),
70 | partialRight(filter, 'site', partialRight(checkRe, urlMatch))
71 | )
72 | }
73 | // Use partial to prefill the above helper with the magic keyword
74 | let totalMagicByProp = partial(totalURLByPropCalculator, 'magic')
75 | // [NOTE] Equivalent to
76 | // let totalMagicByProp = prop => totalURLByPropCalculator('magic', prop)
77 |
78 | // Add the final arg and then execute the function by passing the data array of objects
79 | let totalMagicSignups = totalMagicByProp('signups')(data) // 290
80 | let totalMagicVisits = totalMagicByProp('visits')(data) // 6000
81 |
82 | // Voila we get our answer
83 | let answer = totalMagicSignups / totalMagicVisits // 0.04833
84 |
85 | // compose util
86 | function compose(...fns) {
87 | return function composed(result) {
88 | // copy the array of functions
89 | var list = [...fns];
90 |
91 | while (list.length > 0) {
92 | // take the last function off the end of the list
93 | // and execute it
94 | result = list.pop()(result);
95 | }
96 |
97 | return result;
98 | };
99 | }
100 |
101 | // Using a partial right util (same as partial but to the right)
102 | function partialRight(fn, ...presetArgs) {
103 | return function partiallyApplied(...laterArgs) {
104 | return fn(...laterArgs, ...presetArgs);
105 | };
106 | }
107 |
108 | // Using a partial which allows us to pre-fill arguments for the fn we want to use from the left
109 | function partial (fn, ...presetArgs) {
110 | return function partiallyApplied(...laterArgs) {
111 | return fn(...presetArgs, ...laterArgs);
112 | };
113 | }
114 |
--------------------------------------------------------------------------------
/fp-light/composition/6.js:
--------------------------------------------------------------------------------
1 | // Imperative vs declarative with composition
2 |
3 | // Problem: Compare average male vs female salary over age 30 with university degree
4 | let data = [
5 | {university: true, salary: 300, age: 42, gender: 'male'},
6 | {university: true, salary: 200, age: 35, gender: 'female'},
7 | {university: false, salary: 80, age: 34, gender: 'male'},
8 | {university: true, salary: 300, age: 23, gender: 'female'},
9 | {university: true, salary: 250, age: 52, gender: 'female'},
10 | {university: true, salary: 100, age: 31, gender: 'female'},
11 | {university: true, salary: 200, age: 45, gender: 'male'},
12 | {university: true, salary: 70, age: 18, gender: 'male'},
13 | {university: false, salary: 50, age: 21, gender: 'male'},
14 | {university: true, salary: 100, age: 43, gender: 'female'}
15 | ]
16 |
17 | // 1. Imperative style
18 | let calcAvgSalary = (data, gender, age) => {
19 | let filterGender = data.filter(el => el.gender === gender)
20 | let overAge = filterGender.filter(el => el.age > age)
21 | let salaryArr = overAge.map(el => el.salary)
22 | let totalSalary = salaryArr.reduce((tally, el) => {
23 | return tally + el
24 | }, 0)
25 | let averageSalary = totalSalary / salaryArr.length
26 | return averageSalary.toFixed(0)
27 | }
28 |
29 | let maleAvg = calcAvgSalary(data, 'male', 30) // 193
30 | let femaleAvg = calcAvgSalary(data, 'female', 30) // 163
31 |
32 | // 2. Declarative style
33 | // filterGender --> filterOverAge --> map salary --> sum Salary --> average Salary --> toFix
34 | // More lines of code but easier to see how the function is composed and data gets piped through and transformed
35 |
36 | // We create two helper functions using compose to first extract the property
37 | // and then check it against a predicate function
38 | // We are wrapping into around another function because we want to reuse for different genders and ages
39 | let isGender = (gender) => {
40 | return compose(
41 | partial(isEqual, gender),
42 | partial(prop, 'gender')
43 | )
44 | }
45 | let isOlder = (age) => {
46 | return compose(
47 | partialRight(isGreaterThan, age),
48 | partial(prop, 'age')
49 | )
50 | }
51 |
52 | // Here we have our big compose function which initially to takes an age and gender to create a function
53 | // It then awaits an array to perform the compose steps
54 | // filterGender --> filterOverAge --> map salary --> sum Salary --> average Salary --> toFix
55 | let calcAvgSalary2 = (age, gender) => {
56 | return compose(
57 | partial(toFixed,0),
58 | average,
59 | partialRight(map, 'salary'),
60 | partial(filter, isOlder(age)),
61 | partial(filter, isGender(gender))
62 | )
63 | }
64 | let calcSalaryOver30 = partial(calcAvgSalary2, 30)
65 | let maleAvg2 = calcSalaryOver30('male')(data) // 193
66 | let femaleAvg2 = calcSalaryOver30('female')(data) // 163
67 |
68 |
69 | // Utility functions
70 | function toFixed(dec, num){
71 | return num.toFixed(dec)
72 | }
73 | function isEqual(a, b){
74 | return a === b
75 | }
76 | function isGreaterThan(a, b){
77 | return a > b
78 | }
79 | function prop(name, obj) {
80 | return obj[name];
81 | }
82 | function average(arr){
83 | return arr.reduce(((a, b) => a + b)) / arr.length
84 | }
85 |
86 | function filter(fn, arr) {
87 | return arr.filter(el => fn(el))
88 | }
89 |
90 | function map(arr, prop) {
91 | return arr.map(el => el[prop])
92 | }
93 |
94 | function compose(...fns) {
95 | return function composed(result) {
96 | // copy the array of functions
97 | var list = [...fns];
98 |
99 | while (list.length > 0) {
100 | // take the last function off the end of the list
101 | // and execute it
102 | result = list.pop()(result);
103 | }
104 |
105 | return result;
106 | };
107 | }
108 |
109 | function partialRight(fn, ...presetArgs) {
110 | return function partiallyApplied(...laterArgs) {
111 | return fn(...laterArgs, ...presetArgs);
112 | };
113 | }
114 |
115 | function partial(fn, ...presetArgs) {
116 | return function partiallyApplied(...laterArgs) {
117 | return fn(...presetArgs, ...laterArgs);
118 | };
119 | }
120 |
121 | function sum(arr, prop) {
122 | return arr.reduce((tally, el) => {
123 | return el[prop] + tally
124 | }, 0)
125 | }
126 |
--------------------------------------------------------------------------------
/react/es5time.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ES5 example
10 |
11 |
12 |
13 | ES5 jQuery without React and with undo history
14 |
15 |
16 |
17 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/redux/counter/modular-redux2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Modular Multiple Redux Reducer
13 |
14 |
15 |
16 |
138 |
139 |
140 |
--------------------------------------------------------------------------------
/redux/counter/modular-reducer3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Modular Multiple Reducer
13 |
14 |
15 |
151 |
152 |
153 |
--------------------------------------------------------------------------------
/fp-light/examples/refactor/coin/refactored.js:
--------------------------------------------------------------------------------
1 | let data = [{
2 | id: "bitcoin",
3 | name: "Bitcoin",
4 | symbol: "BTC",
5 | rank: "1",
6 | price_usd: "6830.38",
7 | price_btc: "1.0",
8 | market_cap_usd: "117053840860",
9 | available_supply: "17137237.0",
10 | total_supply: "17137237.0",
11 | max_supply: "21000000.0",
12 | percent_change_1h: "0.08",
13 | percent_change_24h: "2.76",
14 | percent_change_7d: "7.09",
15 | last_updated: "1531029315"
16 | },
17 | {
18 | id: "ethereum",
19 | name: "Ethereum",
20 | symbol: "ETH",
21 | rank: "2",
22 | price_usd: "493.618",
23 | price_btc: "0.0733422",
24 | market_cap_usd: "49635857793.0",
25 | available_supply: "100555202.0",
26 | total_supply: "100555202.0",
27 | max_supply: null,
28 | percent_change_1h: "0.27",
29 | percent_change_24h: "4.4",
30 | percent_change_7d: "8.73",
31 | last_updated: "1531029313"
32 | },
33 | {
34 | id: "ripple",
35 | name: "XRP",
36 | symbol: "XRP",
37 | rank: "3",
38 | price_usd: "0.490227",
39 | price_btc: "0.00007284",
40 | market_cap_usd: "19247510486.0",
41 | available_supply: "39262444717.0",
42 | total_supply: "99991916481.0",
43 | max_supply: "100000000000",
44 | percent_change_1h: "-0.15",
45 | percent_change_24h: "3.18",
46 | percent_change_7d: "6.89",
47 | last_updated: "1531029302"
48 | },
49 | {
50 | id: "bitcoin-cash",
51 | name: "Bitcoin Cash",
52 | symbol: "BCH",
53 | rank: "4",
54 | price_usd: "773.087",
55 | price_btc: "0.114866",
56 | market_cap_usd: "13316713483.0",
57 | available_supply: "17225375.0",
58 | total_supply: "17225375.0",
59 | max_supply: "21000000.0",
60 | percent_change_1h: "0.65",
61 | percent_change_24h: "5.46",
62 | percent_change_7d: "4.7",
63 | last_updated: "1531029315"
64 | },
65 | {
66 | id: "eos",
67 | name: "EOS",
68 | symbol: "EOS",
69 | rank: "5",
70 | price_usd: "9.02347",
71 | price_btc: "0.00134071",
72 | market_cap_usd: "8086378058.0",
73 | available_supply: "896149492.0",
74 | total_supply: "900000000.0",
75 | max_supply: "1000000000.0",
76 | percent_change_1h: "0.07",
77 | percent_change_24h: "4.03",
78 | percent_change_7d: "12.13",
79 | last_updated: "1531029314"
80 | },
81 | {
82 | id: "litecoin",
83 | name: "Litecoin",
84 | symbol: "LTC",
85 | rank: "6",
86 | price_usd: "85.1305",
87 | price_btc: "0.0126488",
88 | market_cap_usd: "4878737095.0",
89 | available_supply: "57308921.0",
90 | total_supply: "57308921.0",
91 | max_supply: "84000000.0",
92 | percent_change_1h: "-0.38",
93 | percent_change_24h: "2.14",
94 | percent_change_7d: "6.01",
95 | last_updated: "1531029301"
96 | },
97 | {
98 | id: "stellar",
99 | name: "Stellar",
100 | symbol: "XLM",
101 | rank: "7",
102 | price_usd: "0.211038",
103 | price_btc: "0.00003136",
104 | market_cap_usd: "3960224302.0",
105 | available_supply: "18765455992.0",
106 | total_supply: "104085355272",
107 | max_supply: null,
108 | percent_change_1h: "0.59",
109 | percent_change_24h: "1.9",
110 | percent_change_7d: "8.63",
111 | last_updated: "1531029306"
112 | },
113 | {
114 | id: "cardano",
115 | name: "Cardano",
116 | symbol: "ADA",
117 | rank: "8",
118 | price_usd: "0.148949",
119 | price_btc: "0.00002213",
120 | market_cap_usd: "3861811230.0",
121 | available_supply: "25927070538.0",
122 | total_supply: "31112483745.0",
123 | max_supply: "45000000000.0",
124 | percent_change_1h: "0.12",
125 | percent_change_24h: "3.76",
126 | percent_change_7d: "8.95",
127 | last_updated: "1531029326"
128 | },
129 | {
130 | id: "iota",
131 | name: "IOTA",
132 | symbol: "MIOTA",
133 | rank: "9",
134 | price_usd: "1.11396",
135 | price_btc: "0.00016551",
136 | market_cap_usd: "3096285554.0",
137 | available_supply: "2779530283.0",
138 | total_supply: "2779530283.0",
139 | max_supply: "2779530283.0",
140 | percent_change_1h: "0.46",
141 | percent_change_24h: "3.27",
142 | percent_change_7d: "9.3",
143 | last_updated: "1531029314"
144 | },
145 | {
146 | id: "neo",
147 | name: "NEO",
148 | symbol: "NEO",
149 | rank: "10",
150 | price_usd: "41.1367",
151 | price_btc: "0.00611213",
152 | market_cap_usd: "2673885500.0",
153 | available_supply: "65000000.0",
154 | total_supply: "100000000.0",
155 | max_supply: "100000000.0",
156 | percent_change_1h: "1.96",
157 | percent_change_24h: "10.43",
158 | percent_change_7d: "35.25",
159 | last_updated: "1531029311"
160 | },
161 | {
162 | id: "tether",
163 | name: "Tether",
164 | symbol: "USDT",
165 | rank: "11",
166 | price_usd: "1.00637",
167 | price_btc: "0.00014953",
168 | market_cap_usd: "2623747830.0",
169 | available_supply: "2607140346.0",
170 | total_supply: "3080109502.0",
171 | max_supply: null,
172 | percent_change_1h: "-0.02",
173 | percent_change_24h: "0.4",
174 | percent_change_7d: "0.71",
175 | last_updated: "1531029306"
176 | },
177 | {
178 | id: "tron",
179 | name: "TRON",
180 | symbol: "TRX",
181 | rank: "12",
182 | price_usd: "0.0380127",
183 | price_btc: "0.00000565",
184 | market_cap_usd: "2499263244.0",
185 | available_supply: "65748111645.0",
186 | total_supply: "100000000000",
187 | max_supply: null,
188 | percent_change_1h: "0.14",
189 | percent_change_24h: "4.2",
190 | percent_change_7d: "1.01",
191 | last_updated: "1531029314"
192 | },
193 | {
194 | id: "monero",
195 | name: "Monero",
196 | symbol: "XMR",
197 | rank: "13",
198 | price_usd: "136.294",
199 | price_btc: "0.0202507",
200 | market_cap_usd: "2208574441.0",
201 | available_supply: "16204488.0",
202 | total_supply: "16204488.0",
203 | max_supply: null,
204 | percent_change_1h: "0.44",
205 | percent_change_24h: "1.93",
206 | percent_change_7d: "4.22",
207 | last_updated: "1531029306"
208 | },
209 | {
210 | id: "dash",
211 | name: "Dash",
212 | symbol: "DASH",
213 | rank: "14",
214 | price_usd: "245.723",
215 | price_btc: "0.0365098",
216 | market_cap_usd: "2010613083.0",
217 | available_supply: "8182437.0",
218 | total_supply: "8182437.0",
219 | max_supply: "18900000.0",
220 | percent_change_1h: "0.19",
221 | percent_change_24h: "1.81",
222 | percent_change_7d: "4.26",
223 | last_updated: "1531029302"
224 | },
225 | {
226 | id: "ethereum-classic",
227 | name: "Ethereum Classic",
228 | symbol: "ETC",
229 | rank: "15",
230 | price_usd: "18.9767",
231 | price_btc: "0.00281957",
232 | market_cap_usd: "1952045552.0",
233 | available_supply: "102865385.0",
234 | total_supply: "102865385.0",
235 | max_supply: null,
236 | percent_change_1h: "0.54",
237 | percent_change_24h: "3.48",
238 | percent_change_7d: "18.99",
239 | last_updated: "1531029307"
240 | },
241 | {
242 | id: "nem",
243 | name: "NEM",
244 | symbol: "XEM",
245 | rank: "16",
246 | price_usd: "0.186682",
247 | price_btc: "0.00002774",
248 | market_cap_usd: "1680138000.0",
249 | available_supply: "8999999999.0",
250 | total_supply: "8999999999.0",
251 | max_supply: null,
252 | percent_change_1h: "-0.08",
253 | percent_change_24h: "0.36",
254 | percent_change_7d: "14.3",
255 | last_updated: "1531029306"
256 | },
257 | {
258 | id: "binance-coin",
259 | name: "Binance Coin",
260 | symbol: "BNB",
261 | rank: "17",
262 | price_usd: "14.3233",
263 | price_btc: "0.00212817",
264 | market_cap_usd: "1633447611.0",
265 | available_supply: "114041290.0",
266 | total_supply: "194972068.0",
267 | max_supply: null,
268 | percent_change_1h: "-0.28",
269 | percent_change_24h: "3.2",
270 | percent_change_7d: "-0.92",
271 | last_updated: "1531029314"
272 | },
273 | {
274 | id: "vechain",
275 | name: "VeChain",
276 | symbol: "VEN",
277 | rank: "18",
278 | price_usd: "2.57263",
279 | price_btc: "0.00038224",
280 | market_cap_usd: "1420523142.0",
281 | available_supply: "552167681.0",
282 | total_supply: "873378637.0",
283 | max_supply: null,
284 | percent_change_1h: "0.02",
285 | percent_change_24h: "4.17",
286 | percent_change_7d: "-2.0",
287 | last_updated: "1531029318"
288 | },
289 | {
290 | id: "tezos",
291 | name: "Tezos",
292 | symbol: "XTZ",
293 | rank: "19",
294 | price_usd: "2.24997",
295 | price_btc: "0.0003343",
296 | market_cap_usd: "1366832117.0",
297 | available_supply: "607489041.0",
298 | total_supply: "763306930.0",
299 | max_supply: null,
300 | percent_change_1h: "7.03",
301 | percent_change_24h: "30.86",
302 | percent_change_7d: "-48.1",
303 | last_updated: "1531029318"
304 | },
305 | {
306 | id: "omisego",
307 | name: "OmiseGO",
308 | symbol: "OMG",
309 | rank: "20",
310 | price_usd: "8.19652",
311 | price_btc: "0.00121785",
312 | market_cap_usd: "836393816.0",
313 | available_supply: "102042552.0",
314 | total_supply: "140245398.0",
315 | max_supply: null,
316 | percent_change_1h: "0.34",
317 | percent_change_24h: "2.68",
318 | percent_change_7d: "2.82",
319 | last_updated: "1531029314"
320 | },
321 | {
322 | id: "qtum",
323 | name: "Qtum",
324 | symbol: "QTUM",
325 | rank: "21",
326 | price_usd: "9.22542",
327 | price_btc: "0.00137072",
328 | market_cap_usd: "817967399.0",
329 | available_supply: "88664516.0",
330 | total_supply: "100664516.0",
331 | max_supply: null,
332 | percent_change_1h: "-0.6",
333 | percent_change_24h: "-0.48",
334 | percent_change_7d: "7.75",
335 | last_updated: "1531029315"
336 | },
337 | {
338 | id: "zcash",
339 | name: "Zcash",
340 | symbol: "ZEC",
341 | rank: "22",
342 | price_usd: "175.325",
343 | price_btc: "0.0260499",
344 | market_cap_usd: "754946163.0",
345 | available_supply: "4305981.0",
346 | total_supply: "4305981.0",
347 | max_supply: null,
348 | percent_change_1h: "-0.44",
349 | percent_change_24h: "-0.08",
350 | percent_change_7d: "5.57",
351 | last_updated: "1531029310"
352 | },
353 | {
354 | id: "ontology",
355 | name: "Ontology",
356 | symbol: "ONT",
357 | rank: "23",
358 | price_usd: "4.71691",
359 | price_btc: "0.00070084",
360 | market_cap_usd: "713631573.0",
361 | available_supply: "151292175.0",
362 | total_supply: "1000000000.0",
363 | max_supply: null,
364 | percent_change_1h: "0.22",
365 | percent_change_24h: "1.25",
366 | percent_change_7d: "-8.58",
367 | last_updated: "1531029326"
368 | },
369 | {
370 | id: "icon",
371 | name: "ICON",
372 | symbol: "ICX",
373 | rank: "24",
374 | price_usd: "1.69981",
375 | price_btc: "0.00025256",
376 | market_cap_usd: "658559666.0",
377 | available_supply: "387431340.0",
378 | total_supply: "400228740.0",
379 | max_supply: null,
380 | percent_change_1h: "-0.13",
381 | percent_change_24h: "0.96",
382 | percent_change_7d: "5.03",
383 | last_updated: "1531029318"
384 | },
385 | {
386 | id: "zilliqa",
387 | name: "Zilliqa",
388 | symbol: "ZIL",
389 | rank: "25",
390 | price_usd: "0.085807",
391 | price_btc: "0.00001275",
392 | market_cap_usd: "650086141.0",
393 | available_supply: "7576143444.0",
394 | total_supply: "12600000000.0",
395 | max_supply: null,
396 | percent_change_1h: "0.77",
397 | percent_change_24h: "3.91",
398 | percent_change_7d: "19.52",
399 | last_updated: "1531029323"
400 | },
401 | {
402 | id: "lisk",
403 | name: "Lisk",
404 | symbol: "LSK",
405 | rank: "26",
406 | price_usd: "5.62616",
407 | price_btc: "0.00083594",
408 | market_cap_usd: "606233819.0",
409 | available_supply: "107752680.0",
410 | total_supply: "122999128.0",
411 | max_supply: null,
412 | percent_change_1h: "0.42",
413 | percent_change_24h: "0.18",
414 | percent_change_7d: "4.53",
415 | last_updated: "1531029310"
416 | },
417 | {
418 | id: "bytecoin-bcn",
419 | name: "Bytecoin",
420 | symbol: "BCN",
421 | rank: "27",
422 | price_usd: "0.00318906",
423 | price_btc: "0.00000047",
424 | market_cap_usd: "586437778.0",
425 | available_supply: "183890481254",
426 | total_supply: "183890481254",
427 | max_supply: "184470000000",
428 | percent_change_1h: "-0.51",
429 | percent_change_24h: "-1.42",
430 | percent_change_7d: "7.51",
431 | last_updated: "1531029306"
432 | },
433 | {
434 | id: "0x",
435 | name: "0x",
436 | symbol: "ZRX",
437 | rank: "28",
438 | price_usd: "0.98548",
439 | price_btc: "0.00014642",
440 | market_cap_usd: "525722536.0",
441 | available_supply: "533468499.0",
442 | total_supply: "1000000000.0",
443 | max_supply: null,
444 | percent_change_1h: "1.16",
445 | percent_change_24h: "0.34",
446 | percent_change_7d: "28.61",
447 | last_updated: "1531029315"
448 | },
449 | {
450 | id: "bitcoin-gold",
451 | name: "Bitcoin Gold",
452 | symbol: "BTG",
453 | rank: "29",
454 | price_usd: "30.7187",
455 | price_btc: "0.0045642",
456 | market_cap_usd: "525623025.0",
457 | available_supply: "17110849.0",
458 | total_supply: "17210849.0",
459 | max_supply: "21000000.0",
460 | percent_change_1h: "0.78",
461 | percent_change_24h: "4.11",
462 | percent_change_7d: "17.54",
463 | last_updated: "1531029318"
464 | },
465 | {
466 | id: "aeternity",
467 | name: "Aeternity",
468 | symbol: "AE",
469 | rank: "30",
470 | price_usd: "2.17628",
471 | price_btc: "0.00032335",
472 | market_cap_usd: "507117793.0",
473 | available_supply: "233020472.0",
474 | total_supply: "273685830.0",
475 | max_supply: null,
476 | percent_change_1h: "0.21",
477 | percent_change_24h: "2.42",
478 | percent_change_7d: "12.48",
479 | last_updated: "1531029314"
480 | }
481 | ]
482 |
483 | // FP utilities
484 | let _ = require('lodash')
485 | let pipe = require('lodash/fp/pipe');
486 |
487 | // Stub to resolve with data
488 | let getPrices = () => Promise.resolve({data})
489 |
490 | // Use destructuring in the param, not much change with original
491 | // Note: we could separate this out further with _.pick and _.setWith but it would be overkill
492 | // ES6 destructuring already makes our lives easier to pick the properties we want
493 | let parseTicker = ({symbol,price_usd,market_cap_usd,percent_change_24h,percent_change_7d}) => {
494 | return Object.assign({}, {
495 | ticker: symbol,
496 | currency: "USD",
497 | price: Number(price_usd),
498 | mktcap: Number(market_cap_usd),
499 | changePctDay: Number(percent_change_24h) / 100,
500 | changePctWeek: Number(percent_change_7d) / 100
501 | })
502 | }
503 |
504 | // Similar to original but more declarative using pipe to say that when we receive the array we will pipe it via 2 functions:
505 | // 1. map it and then parse the tickers
506 | // 2. sort it by marketcap descending
507 | let parseTickers = pipe(
508 | _.partialRight(_.map, parseTicker),
509 | _.partialRight(_.orderBy, 'desc', 'mktcap')
510 | )
511 |
512 | // Then we wrap ths into one promise function which calls the API and when resolves will use the pipe parseTicker function
513 | let fetchTickers = () => getPrices().then(data => parseTickers(data.data))
514 |
515 | // Use case
516 | // fetchTickers().then(data => {
517 | // data
518 | // })
519 |
--------------------------------------------------------------------------------
/fp-light/examples/refactor/google/sampleData.json:
--------------------------------------------------------------------------------
1 | [{
2 | "_id": "59f9ae14706c4779a96a091b",
3 | "updatedAt": "2017-11-01T11:28:23.662Z",
4 | "createdAt": "2017-11-01T11:20:52.125Z",
5 | "post": {
6 | "social": {
7 | "vk": {
8 | "shares": 0
9 | },
10 | "stumbledupon": {
11 | "shares": 0
12 | },
13 | "linkedin": {
14 | "shares": 16
15 | },
16 | "pinterest": {
17 | "shares": 6
18 | },
19 | "gplus": {
20 | "shares": 0
21 | },
22 | "facebook": {
23 | "shares": 147,
24 | "comments": 0,
25 | "likes": 147
26 | }
27 | },
28 | "mainImage": "https://media.coindesk.com/uploads/2017/10/Untitled-design-67.jpg",
29 | "domainRank": 10453,
30 | "text": "Survey: Younger Americans More Interested in Cryptocurrencies, ICOs Oct 31, 2017 at 18:00 News • Investments • Initial Coin Offerings \nYounger Americans appear to be more interested in cryptocurrencies compared to other age groups. \nThat's according to the latest survey published by digital student loan servicer LendEDU, which asked roughly 1,000 U.S. citizens about ether and Ripple's XRP token at the end of October. The questions ranged from whether people had heard of ether and XRP to if they would invest in the cryptocurrencies. \nAccording to the survey, 31.6 percent of respondents have heard of ethereum (with 18.2 percent planning to invest in it) while 22.2 percent have heard of XRP (with 14.8 percent intending to purchase some). \nAfter breaking the results down by age, the data shows that roughly a third of respondents between the ages of 18 and 44 planned to invest in ether, while another third did not. The final third was uncertain. \nA far smaller portion of adults aged 45 and up planned to invest in ethereum, with between 89–98 percent of respondents saying they were either not going to invest or were unsure. \nThe results of the research closely match LendEDU's previous survey , which instead took a focus on bitcoin. In that survey, the majority of respondents who had heard of bitcoin were also in the younger age ranges, while older respondents indicated less awareness of the cryptocurrency. \nThe latest survey also looked at initial coin offerings (ICOs), finding that roughly a quarter of respondents had heard of the blockchain funding model, while 75.1 percent had not. However, there was some uncertainty around the status of ICOs among even those who had heard of them. \nLendEDU said of the results: \n\"In our September survey, we found that 10.69 percent of Americans believed that owning bitcoin was illegal in the United States. In our October survey, we found that 21.00 percent of respondents believe that investing in an initial coin offering is illegal. And, 61.10 percent of respondents were unsure about the legality of investing in an initial coin offering.\" \nOn the other hand, 15.1 percent of respondents said they plan to invest in an ICO in the future. \nDisclosure: CoinDesk is a subsidiary of Digital Currency Group, which has an ownership stake in Ripple. \nSurvey image via Shutterstock The leader in blockchain news, CoinDesk is an independent media outlet that strives for the highest journalistic standards and abides by a strict set of editorial policies . Have breaking news or a story tip to send to our journalists? Contact us at . ...",
31 | "crawled": "2017-10-31T20:10:58.002+02:00",
32 | "title": "Survey: Younger Americans More Interested in Cryptocurrencies, ICOs",
33 | "published": "2017-10-31T20:00:00.000+02:00",
34 | "author": "Nikhilesh De",
35 | "url": "http://omgili.com/ri/.wHSUbtEfZSn_gk062VlfY4LrqtSUM5Bo2mf1FyMc9oZtgcqtaiCaAngVCAlu8wk0kSCR6QXN0Tp7c7FOa7zNjhfhuAb3Q9jGR2LQ_mJnLdDc.fEkVcvVw--",
36 | "site": "coindesk.com",
37 | "uuid": "92eb20008b9405be218e7747e8b76c2c93ed8b28"
38 | },
39 | "topic": [
40 | "cryptocurrency",
41 | "ethereum"
42 | ],
43 | "__v": 0,
44 | "documentSentiment" : {
45 | "score": -0.1,
46 | "magnitude": 0.100
47 | }
48 | },
49 | {
50 | "_id": "59f9ae14706c4779a96a091e",
51 | "updatedAt": "2017-11-01T11:28:23.732Z",
52 | "createdAt": "2017-11-01T11:20:52.156Z",
53 | "post": {
54 | "social": {
55 | "vk": {
56 | "shares": 0
57 | },
58 | "stumbledupon": {
59 | "shares": 0
60 | },
61 | "linkedin": {
62 | "shares": 18
63 | },
64 | "pinterest": {
65 | "shares": 0
66 | },
67 | "gplus": {
68 | "shares": 0
69 | },
70 | "facebook": {
71 | "shares": 115,
72 | "comments": 0,
73 | "likes": 115
74 | }
75 | },
76 | "mainImage": "https://media.coindesk.com/uploads/2017/10/Screen-Shot-2017-10-30-at-10.03.35-PM-e1509415528585.png",
77 | "domainRank": 10453,
78 | "text": "Last Laugh? Dilbert Creator Scott Adams Talks Plans for Token Launch Oct 31, 2017 at 13:30 Features • Investments • Initial Coin Offerings \nWhen Scott Adams announced his company would embark on an initial coin offering (ICO) on his blog Friday, he wrote without sarcasm that the day might be one of the \"biggest\" of his life. \nQuite the statement from the creator of the beloved Dilbert comic strip, readers may be surprised given Adams' recent, pointed take on blockchain mania . But far from just lampooning the technology, Adams is gearing up to get firsthand experience of how it can be used for fundraising. \nWhenHub – a time visualization firm co-founded by Adams – is now preparing to launch a token of its own, preceded by an offering of Simple Agreements for Future Tokens, or SAFTs . \nThe project, according to Adams, allows subject matter experts to sell small amounts of time to clients, who will consult in a Skype-like video call session over the platform, the WhenHub Interface Network (WIN). The new product will facilitate auctions and payments, a system that would be built on top of ethereum using smart contracts , taking the tedium out of payments and invoicing. \nIn interview, Adams explained that he went through much the same process as everyone else: going from asking \"What the hell is this?\" to having days where he kind of started to get it and others where he could see the bigger picture. \nAdams told CoinDesk: \n\"Once you see different applications for the blockchain, you start to see: This is way more than I ever imagined.\" \nAdams went on to explain his recent strips about blockchain aren't intended to inspire others to write off blockchain tech. \nRather, he said, they have been inspired by how difficult it can be to explain the technology. In this way, he reasons the comics reflect the way it feels when someone understands something very new and they have to talk to someone else who doesn't. \n\"This is based on a real technology that I think you can say is tried and true,\" Adams said. \"It's not like a management fad ... It's more like the internet in the early days.\" \nBitcoin, in particular, has impressed Adams because \"the secret inventor merged technology and persuasion,\" he said. His recent writing has focused heavily on the latter topic, including a new book, \" Win Bigly ,\" on how Donald Trump convinced voters to elect him president. \nLater on, Adams would use similar wording when discussing how bitcoin took advantage of the psychology of scarcity, hipness and greed to convince people to adopt it and treat it like it like gold. \n\"It's one of the greatest pieces of persuasion the world's ever seen,\" Adams said. Getting to WIN \nWhenHub's CTO arrived at blockchain after creating previous products that helped people conceptualize and manage time, such as calendar management, timeline visualization and meeting coordination. \nAdams said: \n\"As the world often goes, you start with one good idea and then you discover others that might be more fun and more profitable to chase.\" \nThe company held a hackathon early in the year in which one of the entrants included a blockchain element in his not-quite-completed project. It came to mind for another WhenHub co-founder, CTO Nik Kalyani, when he was speaking to a consultant friend about the struggle to find that next client after completing a project. \nWIN will now allow skilled people to squeeze revenue from gaps in their schedule, while enabling consumers to get knowledgeable answers to very specific questions. Adams said he could have used WIN recently when he spent six months trying to set up a live-streaming studio in his home that could work on all the platforms, such as Facebook and Periscope. \n\"There are just some things you can't Google,\" he said. \nAn hour with someone who has set up a lot of such systems, he continued, would have saved him a lot of time and headache. \nAdams, too, says he gets lots of requests for his time, but finds it difficult to manage all the requests. When he does sit down with people, \"most times they have 15 minutes-worth of questions,\" he said. \nSo experts on WIN can post increments of time as small as 15 minutes on their calendar, setting a minimum price for a consultation. Clients interested in talking to them can then bid for the time slot, and the winning bid is permitted to connect with the expert. \nAfter WhenHub verifies the session took place, client funds are released to the expert via a smart contract. The WHEN token \nEqually important to the network will be its custom cryptocurrency. \nAn ERC-20 token built on top of ethereum, WHEN will manage payments between experts and clients on the WIN platform. By creating its own token, the company is able to seed early adopters with rewards for participation for useful activity without the need to spend fiat money to buy ether to provide those incentives. \n\"This is a case where the token is marketing as well as technology,\" Adams said. \"The two worlds collide.\" \nEarly experts ...",
79 | "crawled": "2017-10-31T15:59:19.009+02:00",
80 | "title": "Last Laugh? Dilbert Creator Scott Adams Talks Plans for Token Launch",
81 | "published": "2017-10-31T15:30:00.000+02:00",
82 | "author": "Brady Dale",
83 | "url": "http://omgili.com/ri/.wHSUbtEfZSn_gk062VlfY4LrqtSUM5BfY9.lY3_UyES2WDSt2WfXvo6cRBdeiFM1rccV.dclcrQXSnLiWPSwAfi87BfQZ7Z7gKyG37A51hQJ4Ksj.X7t1u7TGfrWJil",
84 | "site": "coindesk.com",
85 | "uuid": "1d686e3168b3c770fce175eebb96940fdec7916d"
86 | },
87 | "topic": [
88 | "cryptocurrency",
89 | "ethereum"
90 | ],
91 | "__v": 0,
92 | "documentSentiment": {
93 | "score": -0.1,
94 | "magnitude": 0.100
95 | }
96 | },
97 | {
98 | "_id": "59f9ae14706c4779a96a0920",
99 | "updatedAt": "2017-11-01T11:28:23.745Z",
100 | "createdAt": "2017-11-01T11:20:52.159Z",
101 | "post": {
102 | "social": {
103 | "vk": {
104 | "shares": 0
105 | },
106 | "stumbledupon": {
107 | "shares": 0
108 | },
109 | "linkedin": {
110 | "shares": 0
111 | },
112 | "pinterest": {
113 | "shares": 0
114 | },
115 | "gplus": {
116 | "shares": 0
117 | },
118 | "facebook": {
119 | "shares": 0,
120 | "comments": 0,
121 | "likes": 0
122 | }
123 | },
124 | "mainImage": "https://cointelegraph.com/images/725_Ly9jb2ludGVsZWdyYXBoLmNvbS9zdG9yYWdlL3VwbG9hZHMvdmlldy83ZDIxMjZhZmZhMGUxNzJjMDE5NjU3YjVjNThkZDBiOC5qcGc=.jpg",
125 | "domainRank": 45237,
126 | "text": "Raoul Bova Four Weddings and a Funeral, Blockchain Style The permanence of smart contracts has led to the development of a number of services that allow marriage and marriage contract on the Blockchain. Fintech 577 https://cointelegraph.com/news/four-weddings-and-a-funeral-blockchain-style The permanence of smart contracts has led to the development of a number of services that allow marriage and marriage contract on the Blockchain. Blockchain,Smart Contracts 11240 Total views 215 Total shares Smart contracts have brought the Blockchain closer to real life. After the smart contract is signed, it's submitted for permanent storage as a secure record. This permanence has led to the development of a number of services that allow marriage and marriage contract on the Blockchain. Blockchain is forever The first marriage was registered in the public registry of Blockchain on Oct. 5, 2014. The wedding of David Mondrus and Joyce Bayo took place at a private Bitcoin conference at Disney World in Orlando, Florida. To legalize the marriage, the couple had to scan and confirm the QR code, which was then written directly to the Bitcoin Blockchain. The oath of the newlyweds was: \n\"Life is not eternal and death can separate us, but the Blockchain is forever.\" Witnessing to eternity \nAnother example of a wedding on registered on Blockchain is Bitcoin activist Oles Slobodenyuk and Irina Dukhnovskaya on Jul. 17, 2016. At the celebration, the presenter recorded a marriage certificate in the Weddingbook.io platform on a Blockchain. \nThe site uses the capabilities of Cryptograffiti, a web service that allows people to encode hidden messages into the Bitcoin Blockchain using a special online interface. It is possible not only to record the wedding certificate permanently but also to keep a list of witnesses and wedding vows on a Blockchain. \nDespite the fact that not all marriages are successful, developers foresee a growing number of unions being registered on Blockchains. Some platforms help couples not only register a marriage but also prescribe the conditions of family life in the marriage contract. Smart decisions IT-developers Gaurang Torvekar and Sayalee Kaluskar signed the first and most famous marriage contract on the Blockchain in 2016. The couple downloaded the marriage contract template to the cloud-based File System solution and digitally signed using the Ethereum-based Attores platform. \nThe agreement of Prenup With Love spells out the conditions for cohabitation. For example, the frequency of shopping, the amount of chores done at home, how much time should be spent on night dating, what series they will watch and so on. \nThe contract was activated after both parties sent a coded message from their IP addresses, which was reflected in the smart contract. This contract has an open code and all newlyweds can use it. It should be noted that such contracts do not have legal force yet. The newlyweds commented on their decision as follows: \n\"Speaking about the Blockchain and its endless possibilities, we decided to use it to solve family problems. We believe that to contract a marriage on Blockchain is a smart decision.\" \nThe first marriage in Russia contracted with the help of Blockchain system was registered in October 2017. Vasily Lifanovsky and Alla Tkachenko \nVasily Lifanovsky and Alla Tkachenko combined their crypto-savings into a single family budget. This procedure was carried out on MyWish Platform, aimed at managing cryptocurrency assets in various life situations, including during marriage. Funeral included \nThe idea and implementation of this agreement are significantly different from the Prenup With Love agreement. The essence of the marriage contract on MyWish is that the couple's savings will be combined by a smart contract, where they can transfer funds in the future. In case of termination of the contract, all funds stored on the wallet will be divided in half. To cancel the termination of the contract will be allocated three months, in case of an unwise decision. In case of the death of one party, the funds will be transferred to the account of the heir or the spouse. The husband explains: \n\"When I first learned about the Blockchain, I was struck by the thought that a certain network was created over the existing economic institutions, which we ourselves are influencing. The system inspired me so much that I decided not just to become part of the impending future, but also to influence it. Creating my family I want to make sure that our finances are protected from various circumstances.\" Getting ready for divorce Aside from the existing platforms that allow contracting a marriage now, also new projects for married couples are expected to appear. For instance, in June 2017 on Hackathon forum in Skolkovo, the Moscow team Evolve Team has presented a new project for marriage contracts on Blockchain, the Wedding Chain. This project formula consists of a registry office, marriage contracts and management of t...",
127 | "crawled": "2017-10-31T16:47:06.001+02:00",
128 | "title": "Four Weddings and a Funeral, Blockchain Style",
129 | "published": "2017-10-31T15:55:00.000+02:00",
130 | "author": "CoinTelegraph By Raoul Bova",
131 | "url": "http://omgili.com/ri/.wHSUbtEfZSDAcJIStjdfDkxWnrppft1e2LuVChNgtBZ_21ws..kRxNGDSm_XHUdNsgsyE4rdh72UOD177Gf7u.GblTJXACflkj42hBrP_Q-",
132 | "site": "cointelegraph.com",
133 | "uuid": "1af1af70f80a8ac80897297171ec9576eed6f57d"
134 | },
135 | "topic": [
136 | "cryptocurrency",
137 | "ethereum"
138 | ],
139 | "__v": 0
140 | },
141 | {
142 | "_id": "59f9ae14706c4779a96a092b",
143 | "updatedAt": "2017-11-01T11:28:23.809Z",
144 | "createdAt": "2017-11-01T11:20:52.243Z",
145 | "post": {
146 | "social": {
147 | "vk": {
148 | "shares": 0
149 | },
150 | "stumbledupon": {
151 | "shares": 0
152 | },
153 | "linkedin": {
154 | "shares": 0
155 | },
156 | "pinterest": {
157 | "shares": 0
158 | },
159 | "gplus": {
160 | "shares": 0
161 | },
162 | "facebook": {
163 | "shares": 0,
164 | "comments": 0,
165 | "likes": 0
166 | }
167 | },
168 | "mainImage": "https://s4.reutersmedia.net/resources_v2/images/rcom-default.png",
169 | "domainRank": 408,
170 | "text": "LONDON, Nov 1 (Reuters) - The aggregate value of all cryptocurrencies hit a record high of around $184 billion on Wednesday, according to industry website Coinmarketcap, making their reported market value worth around the same as that of Goldman Sachs and Morgan Stanley combined.\nThe new peak came as the biggest and best-known cryptocurrency, bitcoin, hit a record high of more than $6,500 . That took its own “market cap” - its price multiplied by the number of coins that have been released into circulation - to a record high just shy of $110 billion.\nThe latest surge in bitcoin - which has seen an eye-watering increase of almost 800 percent in the past 12 months - was driven by news on Tuesday that CME Group, the world’s largest derivative exchange operator, would launch bitcoin futures in the fourth quarter of the year.\nThe announcement was seen as a major step in the digital currency’s path toward legitimacy and mainstream financial adoption.\nThe second-most valuable cryptocurrency Ether - sometimes known as “Ethereum”, after the project behind it - was trading slightly down on the day at $302 per coin, having hit a record high of more than $410 in June. (Reporting by Jemima Kelly; Editing by Dhara Ranasinghe)\n ...",
171 | "crawled": "2017-11-01T12:19:06.000+02:00",
172 | "title": "Cryptocurrencies' total value hits record high as bitcoin blasts above $6,500",
173 | "published": "2017-11-01T07:18:00.000+02:00",
174 | "author": "Reuters Editorial",
175 | "url": "http://omgili.com/ri/.wHSUbtEfZRtKw217TY9P0ZJCnfITvC0iCC9r6l.6sUEYOHrphOcCKCmXgO4Lliip8nOyq0Th3JeifSYQQpAC32P9TKfcUJYHJ0CS.51bhMefFV56BxiAhbFV5r2.WJZZX4XpodhK_T3y4QX.TFmw7YFPs9c51ev3aGSb2IxGPD_9_kqEzmliQ9pKUiJtxdL83gW4aJCEhk-",
176 | "site": "reuters.com",
177 | "uuid": "b929f0d0fedddbd389e35e6f596a0a383d1a9f73"
178 | },
179 | "topic": [
180 | "cryptocurrency",
181 | "ethereum"
182 | ],
183 | "__v": 0
184 | },
185 | {
186 | "_id": "59f9ae14706c4779a96a092c",
187 | "updatedAt": "2017-11-01T11:28:23.826Z",
188 | "createdAt": "2017-11-01T11:20:52.249Z",
189 | "post": {
190 | "social": {
191 | "vk": {
192 | "shares": 0
193 | },
194 | "stumbledupon": {
195 | "shares": 0
196 | },
197 | "linkedin": {
198 | "shares": 0
199 | },
200 | "pinterest": {
201 | "shares": 0
202 | },
203 | "gplus": {
204 | "shares": 0
205 | },
206 | "facebook": {
207 | "shares": 0,
208 | "comments": 0,
209 | "likes": 0
210 | }
211 | },
212 | "mainImage": "https://s3.reutersmedia.net/resources/r/?m=02&d=20171101&t=2&i=1207880949&w=&fh=545px&fw=&ll=&pl=&sq=&r=LYNXMPEDA02R6",
213 | "domainRank": 408,
214 | "text": " 27 AM / in 19 minutes Cryptocurrencies' total value hits record high as bitcoin blasts above $6,500 Jemima Kelly 2 Min Read LONDON (Reuters) - The aggregate value of all cryptocurrencies hit a record high of around $184 billion on Wednesday, according to industry website Coinmarketcap, making their reported market value worth around the same as that of Goldman Sachs and Morgan Stanley combined. A copy of bitcoin standing on PC motherboard is seen in this illustration picture, October 26, 2017. Picture taken October 26, 2017. REUTERS/Dado Ruvic The new peak came as the biggest and best-known cryptocurrency, bitcoin, hit a record high of more than $6,500. That took its own “market cap” - its price multiplied by the number of coins that have been released into circulation - to a record high just shy of $110 billion. The latest surge in bitcoin - which has seen an eye-watering increase of almost 800 percent in the past 12 months - was driven by news on Tuesday that CME Group, the world’s largest derivative exchange operator, would launch bitcoin futures in the fourth quarter of the year. The announcement was seen as a major step in the digital currency’s path toward legitimacy and mainstream financial adoption. The second-most valuable cryptocurrency Ether - sometimes known as “Ethereum”, after the project behind it - was trading slightly down on the day at $302 per coin, having hit a record high of more than $410 in June. Reporting by Jemima Kelly; Editing by Dhara Ranasinghe...",
215 | "crawled": "2017-11-01T12:48:37.000+02:00",
216 | "title": "Cryptocurrencies' total value hits record high as bitcoin blasts above $6,500",
217 | "published": "2017-11-01T12:20:00.000+02:00",
218 | "author": "",
219 | "url": "http://omgili.com/ri/.wHSUbtEfZQ8vPZA6toGHkl1dV4_QYkwrQoWXEeAafeO.oLOIHV0X65EOo6gu4HAHPCI70sQzz.I9rE3cqbg1s2lT2gbMrOh5k_r9S6LmnBcBr4qvklDPUfvQfl.Au_frBeGN_63nEy7g2LbX1AgBvfb23llsZobU7oLIoo1pKVIWJv0mWzc68KZLdSzEbeVXz3fYWL_c5YMmtXB63HR.w--",
220 | "site": "reuters.com",
221 | "uuid": "cbd476444889ca717ef8d473953c00d1640f62fb"
222 | },
223 | "topic": [
224 | "cryptocurrency",
225 | "ethereum"
226 | ],
227 | "__v": 0
228 | },
229 | {
230 | "_id": "59f9ae14706c4779a96a092d",
231 | "updatedAt": "2017-11-01T11:28:23.823Z",
232 | "createdAt": "2017-11-01T11:20:52.252Z",
233 | "post": {
234 | "social": {
235 | "vk": {
236 | "shares": 0
237 | },
238 | "stumbledupon": {
239 | "shares": 0
240 | },
241 | "linkedin": {
242 | "shares": 0
243 | },
244 | "pinterest": {
245 | "shares": 0
246 | },
247 | "gplus": {
248 | "shares": 0
249 | },
250 | "facebook": {
251 | "shares": 0,
252 | "comments": 0,
253 | "likes": 0
254 | }
255 | },
256 | "mainImage": "https://s2.reutersmedia.net/resources/r/?m=02&d=20171101&t=2&i=1207881497&w=&fh=545px&fw=&ll=&pl=&sq=&r=LYNXMPEDA02RH",
257 | "domainRank": 408,
258 | "text": "LONDON (Reuters) - The aggregate value of all cryptocurrencies hit a record high of around $184 billion on Wednesday, according to industry website Coinmarketcap, making their reported market value worth around the same as that of Goldman Sachs and Morgan Stanley combined.\nA copy of bitcoin standing on PC motherboard is seen in this illustration picture, October 26, 2017. Picture taken October 26, 2017. REUTERS/Dado Ruvic The new peak came as the biggest and best-known cryptocurrency, bitcoin, hit a record high of more than $6,500. That took its own “market cap” - its price multiplied by the number of coins that have been released into circulation - to a record high just shy of $110 billion.\nThe latest surge in bitcoin - which has seen an eye-watering increase of almost 800 percent in the past 12 months - was driven by news on Tuesday that CME Group, the world’s largest derivative exchange operator, would launch bitcoin futures in the fourth quarter of the year.\nThe announcement was seen as a major step in the digital currency’s path toward legitimacy and mainstream financial adoption.\nThe second-most valuable cryptocurrency Ether - sometimes known as “Ethereum”, after the project behind it - was trading slightly down on the day at $302 per coin, having hit a record high of more than $410 in June.\nReporting by Jemima Kelly; Editing by Dhara Ranasinghe\n ...",
259 | "crawled": "2017-11-01T13:16:54.000+02:00",
260 | "title": "Cryptocurrencies' total value hits record high as bitcoin blasts above $6,500",
261 | "published": "2017-11-01T07:28:00.000+02:00",
262 | "author": "Jemima Kelly",
263 | "url": "http://omgili.com/ri/.wHSUbtEfZRtKw217TY9P0ZJCnfITvC0m30sCHr2utYTkprPTjFAhVGACBG.h9DMa2QdTRjUMmQeL8I5bT83N9DnBwrvLjP_FoYtCX7FSujphTQ1Fpgw8E_L1c4RM22XIZoI2iALAIC35czhYOI6Q_4BMrb8seyoLOTIYg_5WFaSTxxOfoazp5jl.JxvmXk3r6JsNqdaJLQJ8d0UoVNoZw--",
264 | "site": "reuters.com",
265 | "uuid": "fad3731484a504f3aa0b1261f387dd4d21131ce5"
266 | },
267 | "topic": [
268 | "cryptocurrency",
269 | "ethereum"
270 | ],
271 | "__v": 0
272 | },
273 | {
274 | "_id": "59f9ae14706c4779a96a092e",
275 | "updatedAt": "2017-11-01T11:28:23.854Z",
276 | "createdAt": "2017-11-01T11:20:52.273Z",
277 | "post": {
278 | "social": {
279 | "vk": {
280 | "shares": 1
281 | },
282 | "stumbledupon": {
283 | "shares": 0
284 | },
285 | "linkedin": {
286 | "shares": 4
287 | },
288 | "pinterest": {
289 | "shares": 1
290 | },
291 | "gplus": {
292 | "shares": 0
293 | },
294 | "facebook": {
295 | "shares": 26,
296 | "comments": 0,
297 | "likes": 26
298 | }
299 | },
300 | "mainImage": "https://cdn.vox-cdn.com/thumbor/1B77t6STClej0JdZzuFfMFdl8Rg=/0x292:2040x1360/fit-in/1200x630/cdn.vox-cdn.com/uploads/chorus_asset/file/9575839/cryptocurrency_0004.jpg",
301 | "domainRank": 496,
302 | "text": "share After years of ambiguous signals, Russian president Vladimir Putin finally clarified Russia’s stance on cryptocurrencies last week with five presidential orders demanding officials set up a legal framework to handle digital currencies. The orders include plans to tax crypto miners, regulate ICOs, sandbox legislation for newly developed blockchain technologies, and an order that outlines the “formation of a single payment space,” likely part of the digital ruble initiative that the Russian Central Bank has been pursuing. It’s a surprising development that could solidify cryptocurrency acceptance within the Russian Federation. It’s been a big year for cryptocurrencies: Bitcoin and Ethereum have seen exponential growth in value, and ICOs around the world have generated $2.6 billion in investment. It’s also been a year of regulatory scrutiny: governments in China, South Korea, the US, and most recently, Vietnam , have been introducing legislation to address the recent boom. China has banned Bitcoin exchanges and other trading platforms, and South Korea has planned a ban on raising money through ICOs. In the midst of a worldwide regulatory crackdown, however, Russia’s back-and-forth position has befuddled observers. On the one hand, Russia has pointed toward wider acceptance. Over the past year, Russian officials have actively encouraged the development of cryptocurrency mining. In June, Putin met with Vitalik Buterin, the founder of Ethereum, in a meeting many interpreted as a government endorsement of digital currencies. As Putin said at the St. Petersburg International Economic Forum that month, boosting the Russian economy and increasing average household incomes “can be done only by developing innovative technologies, including digital technologies.” Since last year, the Russian Central Bank has been overseeing a group of financial institutions that are testing an Ethereum-like “master chain” to possibly issue digital rubles. “The rush to virtual money is not a fad.” This past July , Putin’s aide on internet matters, Dmitry Marinichev, took international press on a tour of his own cryptocurrency mine. Marinichev had converted what was originally a Soviet era car factory into a cryptocurrency server farm. Speaking over the hum of mining rigs, Marinichev revealed he had started a company called the Russian Miner Coin. \"The rush to virtual money is not a fad or a fleeting phenomenon. The virtualization of our lives is a market process that has gone on and will continue,\" he said, according to AFP . A few weeks later, Marinichev announced that Russian Miner Coin was launching an ICO for RMC virtual tokens, which aimed to raise $100 million worth of bitcoin and Ethereum. The plans were detailed in a media deck that touted Russia’s cold climate and cheap electricity bills. The governor of Russia’s Leningrad region, Alexander Drozdenko, recently invited crypto miners to set up a facility in the vicinity of the Leningrad nuclear power plant in order to take advantage of the area’s cheap electricity. News reports suggest that cryptocurrencies are becoming more visible across the country. This past summer, cafes and restaurants in Moscow began accepting crypto payments . Computer stores throughout the country reported graphic and video card shortages, which crypto miners use to boost their rigs. According to local media , AMD Radeon cards were sold out nationwide in June, and even the remaining units of the Nvidia Geforce GTX 1060 were highly coveted. In August, Burger King rolled out a cryptocurrency in Russia: the Whoppercoin . Just last month, statements from Russia’s finance minister Anton Siluanov indicated that cryptocurrency would soon be treated like regular financial securities, an approach similar to the one adopted by US Securities Exchange Commission. Over the same period, Russia has sent competing signals indicating that a crackdown on cryptocurrency may be imminent. Earlier this month, Putin called for greater cryptocurrency regulation, citing the “serious risks” that virtual currency could pose. Russian officials, including economic development minister Maxim Oreshkin and the Bank of Russia chairman Elvira Nabiullina , have previously referred to cryptocurrency mining and exchange operations as “pyramid schemes.” That characterization is a throwback to Russia’s stance on cryptocurrencies last year, when the Kremlin was considering jailing individuals up to seven years for using or owning bitcoins. Until Putin’s most recent order, that conflicting narrative left observers scratching their heads. The new regulation would outline an approach that would regulate cryptocurrency across Russia tightly, while still not ruling it illegal. “society is ready to accept blockchain technology; the state is not.” Russia’s sometimes contradicting approach to cryptocurrency regulations is “that society is ready to accept blockchain technology; the state is not,” says Vladimir Popov, a crypto-enthusiast and lawyer...",
303 | "crawled": "2017-10-31T18:49:00.008+02:00",
304 | "title": "Inside Russia’s love-hate relationship with Bitcoin",
305 | "published": "2017-10-31T19:36:00.000+02:00",
306 | "author": "theverge.com",
307 | "url": "http://omgili.com/ri/.wHSUbtEfZQrTHDoKYSJbjrpQN.N5MJg_62zFuGp83Se50hSyLZwYu5bRIti0_CSiA7eksJ4GbtuuSVcovaRWs4i7KLAjJp6vIqT5C3U2IZlFbPao_L66p.UFMbSFwnbyGI6_K7QZmB.ag1vU8yVKQ--",
308 | "site": "theverge.com",
309 | "uuid": "73705ef9737a856abd0e7722b5e01b9fded8ec16"
310 | },
311 | "topic": [
312 | "cryptocurrency",
313 | "ethereum"
314 | ],
315 | "__v": 0
316 | },
317 | {
318 | "_id": "59f9ae14706c4779a96a0937",
319 | "updatedAt": "2017-11-01T11:28:23.872Z",
320 | "createdAt": "2017-11-01T11:20:52.326Z",
321 | "post": {
322 | "social": {
323 | "vk": {
324 | "shares": 0
325 | },
326 | "stumbledupon": {
327 | "shares": 0
328 | },
329 | "linkedin": {
330 | "shares": 0
331 | },
332 | "pinterest": {
333 | "shares": 0
334 | },
335 | "gplus": {
336 | "shares": 0
337 | },
338 | "facebook": {
339 | "shares": 0,
340 | "comments": 0,
341 | "likes": 0
342 | }
343 | },
344 | "mainImage": "http://bitcoinist.com/wp-content/uploads/2017/10/confideal-ecosystem-cover.jpg",
345 | "domainRank": null,
346 | "text": "\nConfideal boasts a vibrant ecosystem where the core features of the integrated platform, such as smart contracts and arbitration, unite to form an application that is far more than just the sum of its parts. \n[Note: This is a sponsored article.] \nInternational trade has existed since ships first sailed upon the seas and caravans crossed mountains, but the increasing pace of technological innovation has led to a massive surge in international trade. Contracts and manufacturing details can be hammered out over video conference calls while documents can be digitally signed and sent across the world without a person having to move from their office desk. \nOf course, this increased amount of trade can have potential headaches, especially if a dispute arises, which is where Confideal comes into play. Confideal is an integrated platform based upon the Ethereum blockchain that features an amazing ecosystem of core features that helps facilitate trade while reducing costs. Three Core Pillars \nThe three core pillars of Confideal are: smart contracts, built-in arbitration, and CDL tokens. A number of platforms offer a single feature or two that international traders might be interested in, but Confideal is unique in that it has created a complete ecosystem composed of individual functions that come together into a harmonious whole. \nSmart contracts have been revolutionary in matters of data management, and they offer a tremendous boon to businesses by creating a document that cannot be altered and is openly transparent. Many businesses have failed to take advantage of smart contracts due to the perceived need for programming knowledge and a dedicated online team, but Confideal allows for the creation of Ethereum-based smart contracts without the need for any coding. \nBuilt into the smart contract is the arbitration feature of Confideal. The entity drawing up the contract selects an arbitrator to be used in case of a dispute arises. An important consideration is that all signatories to the smart contract have to agree to the chosen arbitrator. \nLegal professionals and firms apply to Confideal, showing their qualifications and credentials, to become arbitrators on the platform. Arbitration takes one of two routes: both parties can agree to abide by the decision of the arbitrator or an arbitrator can come to a conclusion that can then be used in international courts of law for further enforcement. \nThe final core facet of Confideal is the CDL token, which is the main internal cryptocurrency for the platform. The token was created according to the ERC20 token standard on the Ethereum blockchain and has several uses on the integrated platform. Bringing It All Together \nEach Confideal’s three core features, when viewed independently of each other, is quite beneficial in regards to international trade. However, the symbiotic manner in which they all work together is breathtaking. The features weave together into an intricate dance that reinforce each other and make the overall ecosystem far stronger than each of the individual components. \nMerchants can use CDL tokens to generate smart contracts and act as currency for the payment of the contract, which negates the standard 1% fee. These tokens can also be freely exchanged for other ERC20 tokens. \nThe smart contract, standing alone, is an amazing innovation for trade, but the inclusion of arbitration takes the smart contract to an entirely new level. Merchants not only have a codified document that spells out the conditions of the deal, not to mention the funds held in escrow and that the contract is unable to be illegally modified, but an automatic means for resolving a disagreement that all parties have already agreed upon has been put into place. \nThe dance between arbitrators and CDL tokens continues in the fact that token holders have a number of votes, based upon the number of tokens held, that can be cast to rank specific arbitrators. Every ten tokens equals one vote, so token holders have the right to directly influence the arbitration aspect of Confideal. However, this influence is not absolute as the smart contract that details the arbitrator has to be agreed upon by all interested parties. Then there’s the fact that arbitrators have no details on the principals of the smart contract, so there’s no undue influence of favoring a side that has given votes to that particular arbitrator. \nEach of the core features of Confideal is of great use to merchants, but it’s the combination of all three elements (smart contracts, tokens, and arbitrators) into a cohesive whole that makes the integrated platform far greater than the sum of its parts. Each facet of Confideal reinforces the others, making them stronger and better than they would be on their own. Tokens can be used as payment and to rank arbitrators whilst arbitration is hardwired into the smart contracts that serve as the foundation of the platform. In the end, merchants and arbitrators are united together into wor...",
347 | "crawled": "2017-11-01T01:36:52.012+02:00",
348 | "title": "Confideal - More Than Just the Sum of Its Parts - Bitcoinist.com",
349 | "published": "2017-10-31T21:45:00.000+02:00",
350 | "author": "Jeff Francis",
351 | "url": "http://omgili.com/ri/_0JOtn.4SCpu2xhlqzh54Chnaqr8MS1Gdr1D6bTCFBEPjyPMoEev3ir_fPjeR5Fb",
352 | "site": "bitcoinist.com",
353 | "uuid": "056b343437642caea6ccb3bd78d4cebedde592dc"
354 | },
355 | "topic": [
356 | "cryptocurrency",
357 | "ethereum"
358 | ],
359 | "__v": 0
360 | },
361 | {
362 | "_id": "59f9ae14706c4779a96a0947",
363 | "updatedAt": "2017-11-01T11:28:23.894Z",
364 | "createdAt": "2017-11-01T11:20:52.445Z",
365 | "post": {
366 | "social": {
367 | "vk": {
368 | "shares": 0
369 | },
370 | "stumbledupon": {
371 | "shares": 0
372 | },
373 | "linkedin": {
374 | "shares": 5
375 | },
376 | "pinterest": {
377 | "shares": 6
378 | },
379 | "gplus": {
380 | "shares": 0
381 | },
382 | "facebook": {
383 | "shares": 51,
384 | "comments": 0,
385 | "likes": 51
386 | }
387 | },
388 | "mainImage": "https://media.coindesk.com/uploads/2017/10/socrates.jpg",
389 | "domainRank": 10453,
390 | "text": "Privacy and Scalability? ZoKrates Devcon Announcement Brings Both to Ethereum Nov 1, 2017 at 00:37 Features • Ethereum • Other Public Protocols • Technology News \nScalability and privacy are top of mind for ethereum developers. \nAnd Jacob Eberhardt's new programming language, ZoKrates, aims to address these topics – providing ethereum developers with a toolkit that could help the network realize the full potential of privacy tech zk-snarks. Zk-snarks was popularized by anonymous cryptocurrency zcash, but ethereum's recent hard fork Byzantium included a couple procedures that pave the way for easier use of the tech on its blockchain. \nRevealed during Devcon3, ethereum's annual developer conference, today, ZoKrates allows information to be obscured off-chain and then uploaded into a smart contract that can be verified on the ethereum blockchain without exposing any of the contract's information. \nThe implications of ZoKrates are vast. For one, it's designed to be so simple to use that any ethereum developer can deploy the technology, which could mean privacy features start emerging across ethereum use-cases, from decentralized applications (dapps) to ERC-20 tokens. And second, because zk-snarks compress information, ZoKrates has the potential to help scale the ethereum platform. by moving computations off-chain and lifting some of the burden off the blockchain. \nAs Eberhardt, a PhD researcher at the Technical University Berlin, told CoinDesk: \n\"zk-SNARKs were discussed a lot in the community over the last two years, but the gap between the theoretical concept and its practical application seemed huge. This gap, I try to bridge.\" \nPrivacy – which has generally been seen as a shortcoming of ethereum – and scalability are hot topics at this year's Devcon3 as ethereum developers prepare to put their heads down to make ethereum live up to its high expectations. Private and smart contracts \nWith ZoKrates, an ethereum smart contract – which only executes if certain pre-conditions are met – serves as a way to transfer a zk-snark operation onto the blockchain and to verify that that information is valid. \nAs Eberhardt describes, a ZoKrates contract verifies that an unknown computation occurred correctly, or in his words, a ZoKrates, “transforms a program into a set of conditions.” \nThis moves ethereum one step closer to offering private transactions on its blockchain, the \"practical implementations\" that ethereum's zk-snarks lead Christian Reitwießner, told CoinDesk in September would be missing to make the technology useful directly following the Byzantium hard fork. \nBut like bringing privacy to ethereum in the first place, the endeavor is complex. \nFor one, verifying a zk-snark is still expensive. When encrypted information is read and accepted onto the ethereum blockchain, it costs a lot of computational effort, which in ethereum is measured in units of ‘gas’. \nWhile ZoKrates gets around this in some ways by moving privacy computations off-chain, the on-chain verification process is still quite costly. \nAlthough, Eberhardt has said that ethereum could, through future upgrades, make this process less expensive. Byzantium, for example, already introduced some mechanisms for making private transactions more affordable like gas-subsidized pairings. Steps towards scalability \nAlthough, the cryptography still has hurdles to overcome. Right now it requires quite a lot of \"gas\"- about a quarter of the overall limit placed on ethereum blocks. \nCrucially though, the cost of verifying a ZoKrates contract remains stable at all times, regardless of the complexity of the computation it represents. At present, a ZoKrates verification fee is about 1.6 million gas. Put simply, anything above this figure would be cheaper to run on ZoKrates. \nThe same applies for anything that can't fit inside a single block- because the size of a ZoKrates verification is consistent. So if deployed, the blockchain would be filled with strings of verifications, rather than transactional information. Eberhardt explains that this could \"lead to higher throughput,\" as more verifications could fit inside a block than the computations themselves. \nBut that's not to say there aren't a few roadblocks in the way. \nSpeaking to CoinDesk, Eberhardt identified two major challenges to the software to date. For one, although Eberhardt is currently dedicated to making the language \"more expressive, intuitive and easy to use,\" it's still very much under development. \nEberhardt open sourced the language today, but specifies it's still in the prototyping stage and is not yet ready for deployment. On top of that, Eberhardt is currently working on the project alone, with some input and discussion from zk-snarks ethereum lead Christian Reitwießner, so for that reason, says he can’t predict the development timeline. \nThe other major concern- less of a problem with ZoKrates as it is with zk-snarks itself- is the unfortunate \"trusted setup.\" \nIn the generation of a zk-snark...",
391 | "crawled": "2017-11-01T02:49:41.002+02:00",
392 | "title": "Privacy and Scalability? ZoKrates Devcon Announcement Brings Both to Ethereum",
393 | "published": "2017-11-01T02:37:00.000+02:00",
394 | "author": "Rachel Rose O'Leary",
395 | "url": "http://omgili.com/ri/.wHSUbtEfZSn_gk062VlfY4LrqtSUM5Bcd7t6nv.5wZvh5E8RH6zXf7zV517VwOKfvNj.AEpmpZSe.nLBgIVReT0r4bECA7_w5fn9RefWvMChdnNEnWvWSKttgd7tJgJ",
396 | "site": "coindesk.com",
397 | "uuid": "701040312ba6f0dd2d044802b8d7f4b47e26af0c"
398 | },
399 | "topic": [
400 | "cryptocurrency",
401 | "ethereum"
402 | ],
403 | "__v": 0
404 | },
405 | {
406 | "_id": "59f9ae14706c4779a96a094c",
407 | "updatedAt": "2017-11-01T11:28:23.937Z",
408 | "createdAt": "2017-11-01T11:20:52.485Z",
409 | "post": {
410 | "social": {
411 | "vk": {
412 | "shares": 0
413 | },
414 | "stumbledupon": {
415 | "shares": 0
416 | },
417 | "linkedin": {
418 | "shares": 4
419 | },
420 | "pinterest": {
421 | "shares": 0
422 | },
423 | "gplus": {
424 | "shares": 0
425 | },
426 | "facebook": {
427 | "shares": 46,
428 | "comments": 0,
429 | "likes": 46
430 | }
431 | },
432 | "mainImage": "https://cdn1.i-scmp.com/sites/default/files/styles/620x356/public/images/methode/2017/10/31/fd1873c6-be1c-11e7-b942-6d23cbdef96a_image_hires_173315.JPG?itok=LhTVIXH2",
433 | "domainRank": 8108,
434 | "text": "Some Chinese bitcoin exchanges – having had their trading officially ordered to halt last month as the cryptocurrency comes under renewed scrutiny by the domestic authorities – have turned their attention instead to selling overseas.\nThe mainland’s ban on transaction between renminbi and digital assets at exchanges came into effect on Tuesday. That followed banning fundraising through initial coin offerings in early September.\nBut now it seems some operators have already started looking elsewhere for business.\n\nZB.com, an overseas platform operated by Chinese digital currency exchange chbtc, has said it will make an international trading function available from Wednesday, allowing users to first sign up for accounts and deposits.\nThe Chinese-English bilingual site offers price quotations for nine cryptocurrencies including bitcoin and ethereum, the two largest by value. Transactions can either be denominated in bitcoin or USDT, a token created by Tether which has an equivalent value to US dollar.\nBitcoin in free fall after China’s Bitkan suspends over-the-counter trading \nHuobi and OkCoin, two of China’s other most popular exchanges, revealed their plans on Tuesday evening, the last day they were able to accept renminbi-denominated bitcoin trades.\n“It’s an end, as well as a new beginning,” said OkCoin in a statement on its homepage, while promoting its offshore exchange OKEx, which will allow investors to conduct “consumer-to-consumer trading of digital currencies against legal tenders of many countries.\nIn a similar move, Huobi announced it has launched a Singapore-based professional portal, offering investors peer-to-peer trading services as a means of payment in place of legal tenders. The team will also operate a Hong Kong subsidiary.\nChina’s bitcoin gloom may be Hong Kong’s boon as crypto issuers switch to city’s exchanges \n“Once the work has been completed to help investors withdraw from platforms, and all trading services have halted, we will be able to provide services using US dollars and digital assets to qualified investors worldwide,” said Li Lin, Huobi’s founder said in an open letter.\nInvestors can also choose to trade digital won-denominated assets on the company’s portal based in South Korea, according to Huobi.\nA form of virtual currency created using blockchain technology, cryptocurrencies have been under close regulatory scrutiny after explosive growth raised concerns that a meltdown of the unregulated market could spill over onto China’s financial market and increase instability.\nHowever, the latest moves by exchanges had sparked renewed speculation that China may loosen its grip on the digital currencies after the 19th Party Congress, which ended last week.\nNearly 20 Chinese exchanges announced plans last month to halt digital currency trading by no later than October, after meeting with the country’s central bank.\nWhy has China declared war on bitcoin and digital currencies? \nCryptos are not legal tender in China, and the central bank has placed an outright ban on banks or financial institutions holding them, although such regulation leaves room for individuals to own them.\nEven amid the clampdown, China has remained a major market for bitcoin investors, with as much as 90 per cent of transactions worldwide still taking place in the country, which also generates roughly one in every two new coins created through mining.\nOn the US exchange Bitstamp, bitcoin currently trades at US$6,095, a record high, after surging 49.2 per cent over the past three months....",
435 | "crawled": "2017-10-31T16:15:15.000+02:00",
436 | "title": "As China closes its doors to cryptocurrencies, exchanges shift their attention overseas",
437 | "published": "2017-11-01T03:30:00.000+02:00",
438 | "author": "Sarah Dai",
439 | "url": "http://omgili.com/ri/jHIAmI4hxg_9Yr2dDCKBfTAg9uCFEOuM1rsorOyfHpRw.yTUDBMEBLjW4D6t8oMdvwFGr8GBRFsudLg5FFZA7uQ8S3LGYubJ25dmAe7RSpPJJXwXjj_g5j.am3UUlb7gf0UBPzjABZ37pBCLmALbwu7uyzE.iQ7d",
440 | "site": "scmp.com",
441 | "uuid": "a44b9ef89d7cdf52c1cd169213a63c6d38cbf09e"
442 | },
443 | "topic": [
444 | "cryptocurrency",
445 | "ethereum"
446 | ],
447 | "__v": 0
448 | }
449 | ]
--------------------------------------------------------------------------------