Skip to content

Instantly share code, notes, and snippets.

@dead-claudia
Last active May 2, 2018 22:07
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dead-claudia/81c48a8fa458ef5832d065292ccd1f47 to your computer and use it in GitHub Desktop.
Save dead-claudia/81c48a8fa458ef5832d065292ccd1f47 to your computer and use it in GitHub Desktop.
Proposed parallel async primitives, compared to callbacks + jQuery and RxJS + promises, using samples adapted from previous examples I found and used (I forget my sources...)
// Non-blocking async iterators + Angular 1
function searchWikipedia(term) {
return $http({
url: "http://en.wikipedia.org/w/api.php?&callback=JSON_CALLBACK",
method: "jsonp",
params: {
action: "opensearch",
search: encodeURI(term),
format: "json"
}
})
}
;(async () => {
const ready = throttle(1000)
let last
for await all (let value of observeOnScope($scope, "search")) {
if (!ready()) continue
value = value || ""
if (value !== last) {
last = value
const result = await searchWikipedia(value)
if (!safeApply($scope, () => $scope.data = result)) break
}
}
})()
// Utilities for above
function throttle(delay) {
let date = 0
return () => {
const current = Date.now()
if (current < date) return false
date = current + delay
return true
}
}
function safeApply($scope, func) {
if ($scope.$$destroyed) return false
if ($scope.$$phase || $scope.$root.$$phase) {
func()
} else {
$scope.apply(() => { func() })
}
return true
}
async function *observeOnScope($scope, expr) {
const resolves = []
const values = []
const sub = $scope.$watch(expr, value => {
if (resolves.length) resolves.pop()(value)
else values.push(value)
})
try {
while (true) {
if (values.length) yield values.pop()
else yield new Promise(r => { resolves.push(r) })
}
} finally {
sub.unsubscribe()
}
}
// Non-blocking async iterators
function randInt(range) {
return Math.random() * range | 0
}
async function *getSuggestions(selector) {
yield undefined
const response = await fetch(`https://api.github.com/users?since=${randInt(500)}`)
const listUsers = await response.json()
let current = listUsers[randInt(listUsers.length)]
yield current
// In stream generators, only literal `await value` expressions are deferred.
await all {
for await all (const _ of fromEvent(document.querySelector(".refresh"), "click")) {
yield current = undefined
const response = await fetch(`https://api.github.com/users?since=${randInt(500)}`)
const listUsers = await response.json()
yield current = listUsers[randInt(listUsers.length)]
}
for await all (const _ of fromEvent(document.querySelector(selector), 'click')) {
yield current
}
}
}
;(async () => {
for all (const selector of [".close1", ".close2", ".close3"]) {
for await all (const suggestion of getSuggestions(selector)) {
if (suggestion == null) {
// hide the first suggestion DOM element
} else {
// show the first suggestion DOM element and render the data
}
}
}
})()
// Helper
async function *fromEvent(elem, name) {
const resolves = []
const values = []
function listener(e) {
if (resolves.length) resolves.pop()(e)
else values.push(e)
}
elem.addEventListener(name, listener, false)
try {
while (true) {
if (values.length) yield values.pop()
else yield new Promise(r => { resolves.push(r) })
}
} finally {
elem.removeEventListener(name, listener, false)
}
}
// Callbacks + Angular 1
function searchWikipedia(term, next) {
return $http({
url: "http://en.wikipedia.org/w/api.php?&callback=JSON_CALLBACK",
method: "jsonp",
params: {
action: "opensearch",
search: encodeURI(term),
format: "json"
}
})
.then(next)
}
var last
var sub = $scope.$watch(expr, throttle(function (value) {
value = value || ""
if (value !== last) {
last = value
searchWikipedia(value, safeApply($scope, sub, function (result) {
$scope.data = result
}))
}
}))
// Utilities for above
function throttle(delay, func) {
let date = 0
return function () {
const current = Date.now()
if (current < date) return
date = current + delay
func.apply(this, arguments)
}
}
function safeApply($scope, sub, func) {
return function (value) {
if ($scope.$$destroyed) {
sub.unsubscribe()
} else if ($scope.$$phase || $scope.$root.$$phase) {
func(value)
} else {
$scope.apply(function () { func(value) })
}
}
}
// Callbacks + jQuery
function randInt(range) {
return Math.random() * range | 0
}
function getSuggestions(selector, send) {
send(undefined)
function getUsers(next) {
$.ajax({url: "https://api.github.com/users?since=" + randInt(500)})
.fail(function (e) { console.error(e) })
.done(function (listUsers) { next(listUsers) })
}
getUsers(function (listUsers) {
var current = listUsers[randInt(listUsers.length)]
send(current)
$(".refresh").click(function () {
current = undefined
send(undefined)
getUsers(function (listUsers) {
current = listUsers[randInt(listUsers.length)]
send(current)
})
})
$(selector).click(function () {
send(current)
})
})
}
;[".close1", ".close2", ".close3"].forEach(function (selector) {
getSuggestions(selector, function (suggestion) {
if (suggestion == null) {
// hide the first suggestion DOM element
} else {
// show the first suggestion DOM element and render the data
}
})
})
// RxJS Observables + Angular 1
import {Observable} from "rxjs"
function searchWikipedia(term) {
return Observable.fromPromise($http({
url: "http://en.wikipedia.org/w/api.php?&callback=JSON_CALLBACK",
method: "jsonp",
params: {
action: "opensearch",
search: encodeURI(term),
format: "json"
}
}))
}
safeApply(
$scope,
observeOnScope($scope, "search")
.throttle(1000)
.map(value => value || "")
.distinctUntilChanged()
.flatMapLatest(searchWikipedia)
result => { $scope.data = result }
).subscribe()
// Utilities for above
function safeApply($scope, stream, func) {
return stream
.takeWhile(() => !$scope.$$destroyed)
.tap(data => {
if ($scope.$$phase || $scope.$root.$$phase) {
func(data)
} else {
$scope.apply(() => { func(data) })
}
})
}
function observeOnScope($scope, expr) {
return new Observable(observer => {
return $scope.$watch(expr, value => observer.next(value))
})
}
// RxJS Observables
import {Observable} from "rxjs"
function randInt(range) {
return Math.random() * range | 0
}
function getSuggestions(selector) {
const refreshElem = document.querySelector(".refresh")
const baseElem = document.querySelector(selector)
const refreshClickStream = Rx.Observable.fromEvent(refreshElem, "click")
const responseStream = refreshClickStream.startWith()
.map(() => `https://api.github.com/users?since=${randInt(500)}`)
.flatMap(url => Rx.Observable.fromPromise(
window.fetch(url).then(response => response.json())
))
.map(() => listUsers[randInt(listUsers.length)])
return Rx.Observable.fromEvent(baseElem, "click")
.startWith(undefined)
.combineLatest(responseStream, (_, listUsers) => listUsers)
.merge(refreshClickStream.map(() => undefined).startWith(undefined))
.map(suggestion => ({selector, suggestion}))
}
Rx.Observable.of(".close1", ".close2", ".close3")
.flatMap(selector => getSuggestions(selector))
.subscribe(({selector, suggestion}) => {
if (suggestion == null) {
// hide the selector's suggestion DOM element
} else {
// show the selector's suggestion DOM element and render the data
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment