Skip to content

Instantly share code, notes, and snippets.

@getify
Forked from cowboyd/1-click-to-load.js
Last active December 22, 2021 06:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save getify/572779ee72421c500416946b5afed5f9 to your computer and use it in GitHub Desktop.
Save getify/572779ee72421c500416946b5afed5f9 to your computer and use it in GitHub Desktop.
Handle a stream of clicks by canceling the latest one
// FORKED FROM: https://gist.github.com/cowboyd/74c0fd9e7aa3cccaeda0b84604c0136a
//
// using CAF instead of Effection.
/**
* Implements an operation to fetch the data as described in https://twitter.com/BenLesh/status/1455597411385098242
* The "winner" is unambiguous. It is the request corresponding to the latest click, guaranteed.
* Other important things to note:
*
* 1. The HTTP request is cancelled every time along with its containing task
* 2. click listener is removed automatically at the end of the `clickToLoad` operation
* 3. The event iteration logic is explicit here to demonstrate the concept, but it can easily be abstracted into a separate
*. function. See the `click-to-load-HOF` example below
*/
// to use:
// CAF(clickToLoad)(new cancelToken());
import CAF, { cancelToken, signalRace, tokenCycle } from "caf/caf";
import { onEvent, onceEvent } from "caf/cag";
function *clickToLoad(signal) {
var getNextToken = tokenCycle();
var clicks = onEvent(signal, button, "click");
// Note: can't use `for await..of` here, even though
// `clicks` is an ES2018 async iterable, because we want
// `clickToLoad(..)` to itself be fully cancelable,
// so we just emulate with a `while..true` loop
//
// e.g.,
// for await (let click of clicks) {
//
while (true) {
// part of emulating the `for await..of` loop
if ((yield clicks.next()).done) break;
CAF(function*(signal){
const resp = yield fetch("/sometimes/veryfast/sometimes/veryslow.data", { signal, });
if (resp.ok) {
updateView(yield resp.json());
}
})(
signalRace([ signal, getNextToken().signal ])
);
}
}
// similar to the file forked from, this version doesn't uses a HOF
// to define an abstraction helper, `forEach()`, to clean up
// the boilerplate around the original tweet's code
import CAF, { cancelToken, signalRace, tokenCycle } from "caf/caf";
import { onEvent, onceEvent } from "caf/cag";
function *clickToLoad(signal) {
yield forEach(signal, onEvent(signal, button, "click"), function*(signal){
const resp = yield fetch("/sometimes/veryfast/sometimes/veryslow.data", { signal, });
if (resp.ok) {
updateView(yield resp.json());
}
});
}
// `forEach()` HOF helper, wrapped with CAF behavior
const forEach = CAF(function *forEach(signal,events,spawn) {
var getNextToken = tokenCycle();
while (true) {
if ((yield events.next()).done) break;
CAF(spawn)( signalRace([ signal, getNextToken().signal ]) );
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment