HOWTO: Fix Promise Anti-Patterns with Composable JavaScript

HOWTO: Fix Promise Anti-Patterns with Composable JavaScript

Please take a look at (& star plz) this article’s companion Github project, Escape From Callback Mountain

Project Goal: research & develop better functional language patterns in JavaScript.

The subjects I’m addressing have been explored by many - and rejected by others. I believe the reason for the divide is Google Results Roulette & developers blindly trusting results. Bad/outdated examples lead to broad suspicions of terms like: modular JS, composable JS or functional JS.

Let me start with a confession: I’m guilty of writing the same anti-patterns I criticize below, as I’m sure many JS developers are as well. Nothing I’ve laid out is meant to be personal or even directed at the original authors. I’m merely doing a code review on common patterns - I hope to pass along an understanding of my priorities & critical thinking processes.

Hopefully you will be able to spot the warning signs of bad Promises after groking this project.

  1. CallbackHell.com
  2. StrongLoop
  3. RisingStack
  4. Q Library

CallbackHell.com

CREDIT: http://callbackhell.com/ image


StrongLoop

CREDIT: https://strongloop.com/strongblog/node-js-callback-hell-promises-generators/ image


RisingStack

CREDIT: https://blog.risingstack.com/node-js-async-best-practices-avoiding-callback-hell-node-js-at-scale/ This is a pretty solid article. I only have 1 concern:

image


Q Library

CREDIT: https://github.com/kriskowal/q

The Q library is one of the most used & oldest to be associated with “Promises.” Hence it suffers from aging examples and it’s need to maintain backwards compatibility. I say “associated with ‘Promises’” since I feel Q is really about the deferred pattern.

It may resemble Promises, however I insist it ain’t. It has far too large a surface area for all the wrong reasons. Also the naming convention inconsistently abbreviates names, making it harder to memorize the interface. Methods like when and done are not necessary.

Bottom line: the deferred pattern is a painful anti-pattern - it improves virtually nothing over the typical callback approach.

q first example

q xmlHTTP deferred anti-pattern


One more thing, my work likely has bugs or quality issues - please file an issue here - always appreciated!

Linux Server Benchmarking Scripts

Goal is a simple set of bash scripts which allow rapid assessment of any given hardware.

Currently only CPU & HDD tests are wired up. Other tests are a work-in-progress, added as needed.

CREATE BENCHMARK SHORTCUT ALIASES

# COPY + PASTE THE FOLLOWING TO CREATE FOLDER & MAIN SCRIPT(S)
# Create folder for results & scripts
export BENCH_DIR=$HOME/benchmarks
mkdir -p $BENCH_DIR/results

cat << 'EOT' >> $HOME/benchmarks/bench-library.sh
#!/bin/bash
set -e

# Install some deps
if [ "$(which sysbench)" == "" -o "$(which inxi)" == "" -o "$(which tcpdump)" == "" ]; then
  apt-get update && apt-get install -y sysbench inxi htop iotop tcpdump hddtemp
fi
# Variables
export DATE_TAG=`date +%F` #YYYY-MM-DD
export CPU_CORES="$([ -e /proc/cpuinfo ] && grep -sc ^processor /proc/cpuinfo || sysctl -n hw.ncpu)"
export BENCH_DIR=$HOME/benchmarks/

mkdir -p $BENCH_DIR

function benchCpu() {
  thread_limit=${1:$CPU_CORES}
  prime_limit=${2:-20000}

  if [ $CPU_CORES -lt `expr 1 + $thread_limit` ]; then
    printf "\n\n${yellow}ALERT: Skipping tests limited by \"${thread_limit} thread test\"\n${cyan}Not enough CPU Cores ($CPU_CORES)  ${reset}\n\n"
  else
    printf "\n\n${yellow}ALERT: Skipping tests limited by \"${thread_limit} thread test\"\n${reset}"

    sysbench --test=cpu \
      --cpu-max-prime=${prime_limit} \
      --num-threads=${CPU_CORES} \
      run | tee -a $BENCH_DIR/results/cpu-test.log
  fi
}

# benchSingleDisk seqrd 120G 8K 300
function benchSingleDisk () {
  sysbench --test=fileio --init-rng=on  --file-test-mode=${1:-seqrd} --file-block-size=${3:-64K} \
    --num-threads=${CPU_CORES} --max-time=${4:-180} --file-total-size=${2:-60G} \
    --max-requests=0 run | tee -a $BENCH_DIR/results/sysbench-fileio.log
}


# benchDisk - tests random read & write, and sequential r, and sequential write, before final cleanup.
function benchDisk() {
  #   Generates test files - up to 75% of your free space - in local dir, then runs the 3 tests (up to 20 minutes each)
  freeSpace=`df -k . | tail -1 | awk '{print $4}'`
  freeSpace="${freeSpace//G|T/}"
  testSize=$(awk "BEGIN {print ($freeSpace / 1024 / 1024) * 0.75; exit}")
  testSize=${testSize}G
  printf "####>>> \nWriting $testSize test data to ${PWD}...\n"

  benchSingleDisk seqrd ${testSize} 8K 300
  benchSingleDisk seqwr ${testSize} 8K 300
  benchSingleDisk seqrw ${testSize} 8K 300
  benchSingleDisk rndrd ${testSize} 8K 300
  benchSingleDisk rndwr ${testSize} 8K 300
  benchSingleDisk rndrw ${testSize} 8K 300

  benchSingleDisk seqrd ${testSize} 64K 300
  benchSingleDisk seqwr ${testSize} 64K 300
  benchSingleDisk seqrw ${testSize} 64K 300
  benchSingleDisk rndrd ${testSize} 64K 300
  benchSingleDisk rndwr ${testSize} 64K 300
  benchSingleDisk rndrw ${testSize} 64K 300

  printf "\n\n####>>> \nCOMPLETED TESTS! Great Success!!! \n\n\n"
}

EOT

chmod +x $BENCH_DIR/*.sh
source $HOME/benchmarks/bench-library.sh

Step 2: CREATE RUNNER SCRIPT

cat << 'EOT' >> tee $HOME/benchmarks/run-bench.sh
#!/bin/bash
set -e

source ./bench-library.sh

# Benchmark HDD Speed (in Current Directory)
###########
benchDisk

# Benchmark CPU - trying different thread counts (and work sizes)
# It'll automatically skip test if we don't have enough cores (to have an impact)
# NB: results comparable between different hardware - up to their same CPU CORE #.
###########
benchCpu 1
benchCpu 4
benchCpu 8  50000
benchCpu 12 100000
benchCpu 16 100000
benchCpu 32 250000
benchCpu 48 500000
benchCpu 64 2000000

EOT

chmod +x $BENCH_DIR/*.sh





Usage Examples:

Make sure to source ~/benchmarks/bench-library.sh before running the following commands manually.

benchCpu 8   250000
benchCpu 16  250000
benchDisk
Cloud Tuning: NVMe SSDs are fast path to Radical Speed Boost

Boost Cloud Performance up to 70%

General notes & sections for select hosting providers below.

Amazon Web Services / EC2 / EBS / S3

TLDR; AWS features restrictive hardware & pricing tiers. The i3 hardware series is the only type I would consider from a Price/performance (and absolute performance) perspective.



image

Note the i3.*xlarge is the only hardware to feature competitively priced NVMe storage (ultra-fast +1GB/s speeds). The major limiting factor I found was the real network speed. Servers with advertised speeds “up to 10/Gb/s” struggled to get close to 1/Gb/s (60-80MB/s).

Network tests used up to 9 additional instances in the same availability zone. Any erroneous data points I replaced with a 0. Additional tests are needed as only 1-2 samples were collected.

Head to Head

Credits

Naming things real good

Let’s tackle something deceptively simple & subtle: Naming

I want to avoid the super-fancy-tech-lingo for this article; and hopefully I can illustrate the issue in a more useful fashion.

While covered in exhausting detail before, the subject matter often gets too technical for the novice programmer to draw any practical understanding. You probably don’t need to read this if the following makes sense Boyce Codd Normal Forms

The Problem - by Example

Have you ever designed a data model (in code, Sql, or excel worksheets)? Does the following look familiar?

*** anti-pattern - don't copy-paste ***
* User
  - id
  - avatarUrl
  - email
  - passwordHash

* Agent
  - id
  - primaryPhoto
  - agentName
  - agentEmail
  - agentPhoneMain
  - agentEmailPrimary
  - agentPhonePrimary
  - agentAddressLine1
  - agentCompanyName
  - agentCompanyAddress
  - *userEmail* - 'Pointer' to User table ^^^

If this is familiar to you, I’ll bet you:

  1. Feel any change to your app will necessitate hours of arduous debugging.
  2. Fear ANY Changing Requirements

schema refactor

The Cost of Bad (Naming) Habits

Why is naming a field agentEmailPrimary the worst?

For starters, you are not creating an entirely new object unto the universe. Over-specificity has some traps:

  1. Strong hint some bad object design/seperation is afoot.
  2. ‘Locked’ into highly specific name, means agentEmailPrimary probably make your views and related code 0% reusable, and featuring annoyingly recurring bugs like:
    • Data not syncing between tables (not obvious if user.email needs to propagate to agent.agentEmail or vice-versa - nevermind complexity of manually implementing where & how to enforce this ‘logic’ …)
    • Validation rules/logic are likely duplicated & inconsitent.
    • Increasingly, your project will resemble a shaky Jenga tower.
    • Fragility piles up with every single new file, as an extremely high attention to detail is required for even trivial changes
  3. agentEmailPrimary could mean a few different things. Avoid ambiguity with shorter names.
    • Watch out for silly excess wording. Primary? Just leads to more questions: Is there a Secondary? Is it for their Primary Next-of-kin?

Hang in there, we’re almost to the solution…

A Solution

// Dan's Recommended Schema Consolidation:

User
  - id
  - role: ['agent', 'lead', 'admin']
  - name
  - phone
  - address
  - email
  - passwordHash
  - company
    - name
    - address

I removed the Agent table, as it didn’t contain fields which were uniquely related to Agents.

All changes were made with these general ideas in mind:

  1. Eliminate unessesary tables. If you have a few dozen tables, this step is mandatory.
  2. Try merge related tables. Important if you are coming from a SQL background to No-SQL
  3. Delete redundant data collection (e.g. remove ActivityLogs table if replaced by Google Analytics)
  4. Try keeping all field names to a single word/noun/pro-noun.
  5. There is no such thing as Agent.agentEmail or Agent.agentPhonePrimary. Period.
  6. By using Highly Specific Names, you cast-in-stone a specific level of code-reusability and durability, well, specifically ZERO %.
  7. Don’t think you are doing yourself any favors with crap like this User.profileSummaryEmail (where ‘profile’ could include contact details for a personal ads site) . This is probably a good point to create a new table, say Profiles which includes Profiles.email.

And if you think I’ve just got crazy ideas, hopefully this will help:

fuck this

Recommended reading includes:

  1. Book: Code Complete
  2. http://phlonx.com/resources/nf3/
  3. https://en.wikipedia.org/wiki/Database_normalization
MongoDB Tuning: disable-transparent-hugepages fix for Debian/Ubuntu

MongoDB Tuning: disable-transparent-hugepages fix for Debian/Ubuntu

Seeing: “WARNING: /sys/kernel/mm/transparent_hugepage/defrag is ‘always’.” ?

Run the following commands to quickly do what MongoDB describes at greater length.

  # Currently just debian
  sudo curl -sSL -o /etc/init.d/disable-transparent-hugepages https://gist.githubusercontent.com/justsml/5e8f10892070072c4ffb/raw/disable-transparent-hugepages
  sudo chmod 755 /etc/init.d/disable-transparent-hugepages
  sudo update-rc.d disable-transparent-hugepages defaults

References:

  1. https://docs.mongodb.org/v3.0/tutorial/transparent-huge-pages/

Tag Cloud

Jekyll::Drops::SiteDrop