Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Asynchronous Event Handling in Javascript and R...

Asynchronous Event Handling in Javascript and React

I'll take you through the history of asynchronous programming in Javascript and show you how generator functions and especially redux-sagas make asynchronous event handling very pleasant for the developer. I showcase this with an example app which deals with bluetooth signals.

Avatar for André Kovac

André Kovac

June 11, 2019
Tweet

More Decks by André Kovac

Other Decks in Programming

Transcript

  1. @andrekovac What?" • Road of asynchronous javascript • Redux-sagas •

    Generators • Use case: Async bluetooth signals
  2. @andrekovac Falafel Example const homeIngredients = { garlic: 2, //

    clove onion: 1, // the thing coriander: 1, // teaspoon cumin: 1, // teaspoon salt: 1, // teaspoon pepper: 1 // "to taste" }; const money = 10; // should be enough for chickpeas const eatFalafel = allFalafel => { allFalafel.forEach(falafel => { console.log(`Yummy falafel with id ${falafel.id}`); }); };
  3. @andrekovac const homeIngredients = { garlic: 2, // clove onion:

    1, // the thing coriander: 1, // teaspoon cumin: 1, // teaspoon salt: 1, // teaspoon pepper: 1 // "to taste" }; const money = 10; // should be enough for chickpeas const eatFalafel = allFalafel => { allFalafel.forEach(falafel => { console.log(`Yummy falafel with id ${falafel.id}`); }); }; Falafel Example
  4. @andrekovac const homeIngredients = { garlic: 2, // clove onion:

    1, // the thing coriander: 1, // teaspoon cumin: 1, // teaspoon salt: 1, // teaspoon pepper: 1 // "to taste" }; const money = 10; // should be enough for chickpeas const eatFalafel = allFalafel => { allFalafel.forEach(falafel => { console.log(`Yummy falafel with id ${falafel.id}`); }); }; Falafel Example
  5. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Make first Eat later Synchronous world
  6. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; 
 const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code
  7. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code
  8. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code
  9. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code
  10. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code
  11. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Synchronous blocking code Every developer loves it!
  12. @andrekovac const makeFalafel = (money, ingredients) => { const chickpeas

    = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Blocking is easy but doesn’t work in a world with users…
  13. @andrekovac const makeFalafel = ( money, ingredients, eatFalafelCallback ) =>

    { getChickpeasCallbackFunction(money, (error, chickpeas) => { if (error) { eatFalafelCallback(error); } if (chickpeas) { getMixtureCallbackFunction( { ...chickpeas, ...ingredients }, (error, mixture) => { if (error) { eatFalafelCallback(error); } if (mixture) { deepFryCallbackFunction(mixture, (error, falafel) => { if (error) { eatFalafelCallback(error); } if (falafel) { eatFalafelCallback(falafel); } }); } } ); } }); }; makeFalafel(money, homeIngredients, eatFalafel); 1. Make world of callbacks
  14. @andrekovac const makeFalafel = ( money, ingredients, eatFalafelCallback ) =>

    { getChickpeasCallbackFunction(money, (error, chickpeas) => { if (error) { eatFalafelCallback(error); } if (chickpeas) { getMixtureCallbackFunction( { ...chickpeas, ...ingredients }, (error, mixture) => { if (error) { eatFalafelCallback(error); } if (mixture) { deepFryCallbackFunction(mixture, (error, falafel) => { if (error) { eatFalafelCallback(error); } if (falafel) { eatFalafelCallback(falafel); } }); } } ); } }); }; makeFalafel(money, homeIngredients, eatFalafel); 1. Make 2. Pass the desire to eat world of callbacks
  15. @andrekovac const makeFalafel = ( money, ingredients, eatFalafelCallback ) =>

    { getChickpeasCallbackFunction(money, (error, chickpeas) => { if (error) { eatFalafelCallback(error); } if (chickpeas) { getMixtureCallbackFunction( { ...chickpeas, ...ingredients }, (error, mixture) => { if (error) { eatFalafelCallback(error); } if (mixture) { deepFryCallbackFunction(mixture, (error, falafel) => { if (error) { eatFalafelCallback(error); } if (falafel) { eatFalafelCallback(falafel); } }); } } ); } }); }; makeFalafel(money, homeIngredients, eatFalafel); 1. Make 2. Pass the desire to eat 3. Finally eat world of callbacks
  16. @andrekovac Pyramid of falafel doom const makeFalafel = ( money,

    ingredients, eatFalafelCallback ) => { getChickpeasCallbackFunction(money, (error, chickpeas) => { if (error) { eatFalafelCallback(error); } if (chickpeas) { getMixtureCallbackFunction( { ...chickpeas, ...ingredients }, (error, mixture) => { if (error) { eatFalafelCallback(error); } if (mixture) { deepFryCallbackFunction(mixture, (error, falafel) => { if (error) { eatFalafelCallback(error); } if (falafel) { eatFalafelCallback(falafel, "callback"); } }); } } ); } }); }; makeFalafel(money, homeIngredients, eatFalafel);
  17. @andrekovac ECMAScript Committee “How can we make async programming easier?”

    Jafar Husain in his Talk “The Evolution of JavaScript”
  18. @andrekovac Promise const getChickpeas = money => new Promise((resolve, reject)

    => setTimeout(() => { if (!money) reject('No cash!'); resolve(buyChickpeas(money)); }, 1000) ); const getMixture = ingredients => new Promise((resolve, reject) => setTimeout(() => { if (!ingredients) reject('No stuff!'); resolve(mix(ingredients)); }, 1000) ); const deepFry = mixture => new Promise((resolve, reject) => setTimeout(() => { if (!mixture) reject('Nothing mixed!'); resolve(throwIntoPan(mixture)); }, 1000) ); Pending ⏱ Fulfilled ✅ Rejected ❌
  19. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  20. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  21. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  22. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  23. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  24. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); Promise
  25. @andrekovac const makeFalafel = (money, ingredients) => { return getChickpeas(money)

    .then(chickpeas => getMixture({ ...chickpeas, ...ingredients })) .then(mixture => deepFry(mixture)) .catch(error => console.error("Falafel error", error)); }; makeFalafel(money, homeIngredients).then(falafel => eatFalafel(falafel) ); const makeFalafel = (money, ingredients) => { const chickpeas = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; }; const falafel = makeFalafel(money, homeIngredients); eatFalafel(falafel); Better, but not too similar yet! Promise chain Synchronous code
  26. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; const makeFalafel = (money, ingredients) => { const chickpeas = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; };
  27. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; const makeFalafel = (money, ingredients) => { const chickpeas = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; };
  28. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; const makeFalafel = (money, ingredients) => { const chickpeas = getChickpeas(money); const mixture = getMixture({ ...chickpeas, ...ingredients }); const falafel = deepFry(mixture); return falafel; };
  29. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  30. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  31. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  32. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  33. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  34. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  35. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; makeFalafelAsync(money, homeIngredients).then(falafel => eatFalafel(falafel) );
  36. @andrekovac Async-await const makeFalafelAsync = async (money, ingredients) => {

    const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; Nice, but how do I use it together with React? * +
  37. @andrekovac React + Redux 1. Single source of truth 2.

    State is read-only (no setters) 3. Changes are made with pure functions “Can you help me with my asynchronous events?”
  38. @andrekovac Redux-thunk const makeFalafel = (money, ingredients) => { return

    async dispatch => { try { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ chickpeas, ...ingredients }); const falafel = await deepFry(mixture); dispatch({ type: CREATE_FALAFELS_SUCCESS, falafel }); return falafel; } catch (error) { dispatch({ type: CREATE_FALAFELS_ERROR, error }); return error; } }; };
  39. @andrekovac Redux-thunk const makeFalafel = (money, ingredients) => { return

    async dispatch => { try { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ chickpeas, ...ingredients }); const falafel = await deepFry(mixture); dispatch({ type: CREATE_FALAFELS_SUCCESS, falafel }); return falafel; } catch (error) { dispatch({ type: CREATE_FALAFELS_ERROR, error }); return error; } }; };
  40. @andrekovac Redux-thunk const makeFalafel = (money, ingredients) => { return

    async dispatch => { try { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ chickpeas, ...ingredients }); const falafel = await deepFry(mixture); dispatch({ type: CREATE_FALAFELS_SUCCESS, falafel }); return falafel; } catch (error) { dispatch({ type: CREATE_FALAFELS_ERROR, error }); return error; } }; };
  41. @andrekovac Redux-thunk const makeFalafel = (money, ingredients) => { return

    async dispatch => { try { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ chickpeas, ...ingredients }); const falafel = await deepFry(mixture); dispatch({ type: CREATE_FALAFELS_SUCCESS, falafel }); return falafel; } catch (error) { dispatch({ type: CREATE_FALAFELS_ERROR, error }); return error; } }; };
  42. @andrekovac Redux-thunk const makeFalafel = (money, ingredients) => { return

    async dispatch => { try { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ chickpeas, ...ingredients }); const falafel = await deepFry(mixture); dispatch({ type: CREATE_FALAFELS_SUCCESS, falafel }); return falafel; } catch (error) { dispatch({ type: CREATE_FALAFELS_ERROR, error }); return error; } }; };
  43. @andrekovac Saga yield take(CREATE_FALAFELS_INIT); Listens for action(s) yield take(CREATE_FALAFELS_INIT); yield

    takeEvery(CREATE_FALAFELS_INIT); yield takeLatest(CREATE_FALAFELS_INIT);
  44. @andrekovac Saga yield put(createFalafelsSuccess()); dispatches an action Listens for action(s)

    yield take(CREATE_FALAFELS_INIT); yield takeEvery(CREATE_FALAFELS_INIT); yield takeLatest(CREATE_FALAFELS_INIT);
  45. @andrekovac Saga yield call(fetchWeatherApi); Call APIs / other functions/sagas yield

    select(falafelSelector); Retrieve a slice of the redux store
  46. @andrekovac Saga yield call(fetchWeatherApi); yield take(CREATE_FALAFELS_INIT); Call APIs / other

    functions/sagas Start other saga in parallel yield fork(chickpeaCleaningSaga); yield select(falafelSelector); Retrieve a slice of the redux store
  47. @andrekovac Generator functions as iterators function* helloWorld() { yield 'hello';

    yield 'world'; return '!'; } Generator * Caller/ Runner const generator = helloWorld(); console.log(generator.next()); console.log(generator.next()); console.log(generator.next());
  48. @andrekovac Generator functions as iterators function* helloWorld() { yield 'hello';

    yield 'world'; return '!'; } Caller/ Runner const generator = helloWorld(); console.log(generator.next()); console.log(generator.next()); console.log(generator.next()); Generator *
  49. @andrekovac Generator functions as iterators Caller/ Runner const generator =

    helloWorld(); console.log(generator.next()); console.log(generator.next()); console.log(generator.next()); Generator * function* helloWorld() { yield 'hello'; yield 'world'; return '!'; }
  50. @andrekovac Generator functions as iterators Caller/ Runner const generator =

    helloWorld(); console.log(generator.next()); console.log(generator.next()); console.log(generator.next()); Generator * function* helloWorld() { yield 'hello'; yield 'world'; return '!'; }
  51. @andrekovac Generator functions as iterators Caller/ Runner const generator =

    helloWorld(); console.log(generator.next()); console.log(generator.next()); console.log(generator.next()); Generator * function* helloWorld() { yield 'hello'; yield 'world'; return '!'; } Demo
  52. @andrekovac Generator functions as iterators function* generatorFunction(i) { let output

    = 0; while (true) { output += 10; yield i + output; } } Demo
  53. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator *
  54. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp);
  55. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp);
  56. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp);
  57. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp);
  58. @andrekovac Generator function …and observers at the same time const

    buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } Generator * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp);
  59. @andrekovac Generator function …and observers at the same time Generator

    * Caller/ Runner const generator = supermarket(); const cp = generator.next().value; generator.next(cp); const buyChickpeas = money => ({ chickpeas: 100 }); function* supermarket() { const chickpeas = yield buyChickpeas(); return chickpeas; } done: true
  60. @andrekovac Async falafel revisited const makeFalafel = async (money, ingredients)

    => { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; };
  61. @andrekovac const makeFalafel = async (money, ingredients) => { const

    chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; const makeFalafel = function* makeFalafel(money, ingredients) { const chickpeas = yield getChickpeas(money); const mixture = yield getMixture({ ...chickpeas, ...ingredients }); const falafel = yield deepFry(mixture); return falafel; }; Async/await VS Generator functions
  62. @andrekovac const makeFalafel = function* makeFalafel(money, ingredients) { const chickpeas

    = yield getChickpeas(money); const mixture = yield getMixture({ ...chickpeas, ...ingredients }); const falafel = yield deepFry(mixture); return falafel; }; const makeFalafel = async (money, ingredients) => { const chickpeas = await getChickpeas(money); const mixture = await getMixture({ ...chickpeas, ...ingredients }); const falafel = await deepFry(mixture); return falafel; }; Demo Async/await VS Generator functions
  63. @andrekovac Async/await V8 engine implementation of async/await is done with

    generators https://chromium.googlesource.com/v8/v8.git/+/d08c0304c5779223d6c468373af4815ec3ccdb84/src/js/harmony-async-await.js#34
  64. @andrekovac Bluetooth Low Energy Beacons Applications
 
 Retail Museums Cars

    Games https://proxy.duckduckgo.com/iu/?u=https%3A%2F%2Fwww.mac5.ca%2Fwp-content%2Fuploads%2F2017%2F12%2Fbeacon-girl.jpg&f=1
  65. @andrekovac iBeacon { name: string uniqueId: string firmwareVersion: string batteryLevel:

    number batteryPowered: boolean transmissionPower: number hasConfigurationProfile: boolean shuffled: boolean locked: boolean model: string peripheral: string rssi: number updatedAt: number } { uuid: string major: number minor: number }
  66. @andrekovac Eddystone Beacon { namespace: string instanceId: string url: string

    eid: string encryptedTelemetry: string, telemetry: { batteryVoltage: number temperature: number, pduCount: number timeSincePowerUp: number, version: number, } } { uuid: string major: number minor: number }
  67. @andrekovac takeLatest const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let

    lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); under the hood
  68. @andrekovac takeLatest const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let

    lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); under the hood
  69. @andrekovac takeLatest const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let

    lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); under the hood
  70. @andrekovac takeLatest const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let

    lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); under the hood
  71. @andrekovac takeLatest const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let

    lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); under the hood
  72. @andrekovac Returned values and uncaught errors propagate upward Cancellation propagates

    downward. cancel const discoverBeaconsWatcherTakeLatest = () => fork(function*() { let lastTask; while (true) { yield take(BEACON_DISCOVERY_INIT); if (lastTask) { yield cancel(lastTask); } lastTask = yield fork(beaconDiscoverySaga); } }); ⬇ ⬆
  73. @andrekovac export const setupBeaconDiscoveryChannel = () => eventChannel<Array<Beacon>>(emitter => {

    let listener; // Add beacon listener listener = kontaktEmitter && kontaktEmitter.addListener("didDiscoverDevices", ({ beacons }) => { emitter(beacons); }); // Unsubscribe function return () => { if (listener) listener.remove(); }; }); Redux-saga event channels
  74. @andrekovac export const setupBeaconDiscoveryChannel = () => eventChannel<Array<Beacon>>(emitter => {

    let listener; // Add beacon listener listener = kontaktEmitter && kontaktEmitter.addListener("didDiscoverDevices", ({ beacons }) => { emitter(beacons); }); // Unsubscribe function return () => { if (listener) listener.remove(); }; }); Redux-saga event channels
  75. @andrekovac export const setupBeaconDiscoveryChannel = () => eventChannel<Array<Beacon>>(emitter => {

    let listener; // Add beacon listener listener = kontaktEmitter && kontaktEmitter.addListener("didDiscoverDevices", ({ beacons }) => { emitter(beacons); }); // Unsubscribe function return () => { if (listener) listener.remove(); }; }); Redux-saga event channels
  76. @andrekovac export const setupBeaconDiscoveryChannel = () => eventChannel<Array<Beacon>>(emitter => {

    let listener; // Add beacon listener listener = kontaktEmitter && kontaktEmitter.addListener("didDiscoverDevices", ({ beacons }) => { emitter(beacons); }); // Unsubscribe function return () => { if (listener) listener.remove(); }; }); Redux-saga event channels
  77. @andrekovac export const setupBeaconDiscoveryChannel = () => eventChannel<Array<Beacon>>(emitter => {

    let listener; // Add beacon listener listener = kontaktEmitter && kontaktEmitter.addListener("didDiscoverDevices", ({ beacons }) => { emitter(beacons); }); // Unsubscribe function return () => { if (listener) listener.remove(); }; }); Redux-saga event channels
  78. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Start beacon discovery
  79. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Start beacon discovery
  80. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Notify redux store
  81. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Setup beacon channel
  82. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Infinite loop
  83. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Listen for beacon signals
  84. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Send discovered beacons to reducer
  85. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Notify store in case of error
  86. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; React if cancelled
  87. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Close beacon channel -> Will trigger unsubscribe function.
  88. @andrekovac export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield

    call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); const beaconChannel = yield call(setupBeaconDiscoveryChannel); try { while (true) { const discoveredBeacons = yield take(beaconChannel); if (discoveredBeacons) { yield put(beaconDiscoverySuccess(discoveredBeacons)); } } } catch (error) { yield put(beaconDiscoveryError(error)); } finally { if (yield cancelled()) { yield call(beaconChannel.close); yield put(beaconDiscoveryStop()); } } }; Notify store
  89. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  90. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void> { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js
  91. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); });
  92. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); });
  93. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); });
  94. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); }); { value: , done: boolean }
  95. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); });
  96. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); });
  97. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); });
  98. @andrekovac Unit Tests export const beaconDiscoverySaga = function* beaconDiscoverySaga(): Saga<void>

    { yield call(Kontakt.init); yield call(Kontakt.configure, { invalidationAge: 2000 }); yield call(Kontakt.startDiscovery); yield put(beaconDiscoveryStart()); ... sagas.js sagas.test.js it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init); expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); });
  99. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  100. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  101. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  102. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  103. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  104. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  105. @andrekovac it("initializes Kontaktio", () => { const expectedYield = call(Kontakt.init);

    expect(generator.next().value).toEqual(expectedYield); }); it("configures beacon library", () => { const expectedYield = call(Kontakt.configure, { invalidationAge: 2000 }); expect(generator.next().value).toEqual(expectedYield); }); it("start beacon discovery", () => { const expectedYield = call(Kontakt.startDiscovery); expect(generator.next().value).toEqual(expectedYield); }); it("notifies the store that beacon discovery has begun", () => { const expectedYield = put(beaconDiscoveryStart()); expect(generator.next().value).toEqual(expectedYield); }); Unit Tests
  106. @andrekovac Try it yourself! Let your computer be a bluetooth

    beacon MactAsBeacon https://github.com/ timd/MactsAsBeacon