From 1579b227605ced6134a8c0b8d49d989c0080ef8a Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sun, 10 Apr 2022 22:33:17 -0600 Subject: [PATCH 01/16] update WIP --- .../concept/fruit-picker/.meta/exemplar.js | 50 -------- .../concept/fruit-picker/fruit-picker.js | 48 +++----- .../concept/fruit-picker/fruit-picker.spec.js | 114 +----------------- exercises/concept/fruit-picker/global.d.ts | 31 +++-- exercises/concept/fruit-picker/grocer.js | 75 ++---------- 5 files changed, 47 insertions(+), 271 deletions(-) diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index 22ca6bf934..e69de29bb2 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -1,50 +0,0 @@ -/// -// @ts-check - -import { checkStatus, checkInventory } from '../grocer'; - -/** - * Returns the service status as a boolean value - * @return {boolean} - */ -export function isServiceOnline() { - return checkStatus((serviceStatus) => serviceStatus === 'ONLINE'); -} - -/** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory - */ -export function pickFruit(variety, quantity, callback) { - return checkInventory({ variety, quantity }, callback); -} - -/** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' - */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - if (err) { - throw new Error(err); - } - - return isAvailable ? 'PURCHASE' : 'NOOP'; -} - -/** - * Pick a fruit, and if it is available, purchase it - * - * @param {string} variety - * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' - */ -export function pickAndPurchaseFruit(variety, quantity) { - return pickFruit(variety, quantity, purchaseInventoryIfAvailable); -} diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index 91d2147d3a..ffb71c033d 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -9,46 +9,32 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { checkStatus, checkInventory } from './grocer'; +import { order } from './grocer'; /** - * Returns the service status as a boolean value - * @return {boolean} + * @return {FruitPickerSuccess} */ -export function isServiceOnline() { - throw new Error('Implement the isServiceOnline function'); +export function onSuccess() { + return { message: 'SUCCESS' } } /** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory + * @return {FruitPickerError} */ -export function pickFruit(variety, quantity, callback) { - throw new Error('Implement the pickFruit function'); +export function onError() { + return { message: 'ERROR'} } /** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean | undefined} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccess + * @param {FruitPickerErrorCallback} onError + * @return void */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - throw new Error('Implement the purchaseInventoryIfAvailable function'); -} - -/** - * Pick a fruit, and if it is available, purchase it - * - * @param {string} variety - * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' - */ -export function pickAndPurchaseFruit(variety, quantity) { - throw new Error('Implement the pickAndPurchaseFruit function'); +export function orderFromGrocer( + query, + onSuccess, + onError +) { + order(query, onSuccess, onError) } diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index 21535916ff..8a2b28b6f3 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -1,116 +1,10 @@ // @ts-check import { - purchaseInventoryIfAvailable, - isServiceOnline, - pickAndPurchaseFruit, - pickFruit, + onError, + onSuccess, + orderFromGrocer } from './fruit-picker'; import { - setStatus, - resetStatus, - setResponse, - getLastQuery, - resetQuery, + order } from './grocer'; - -describe('service status', () => { - beforeEach(() => { - resetStatus(); - }); - - test('returns the initial status of the service', () => { - expect(isServiceOnline()).toBe(false); - }); - - test('returns the status when service is online', () => { - setStatus('ONLINE'); - expect(isServiceOnline()).toBe(true); - }); -}); - -describe('inventory service', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - pickFruit('strawberry', 5, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'strawberry', - quantity: 5, - }); - }); - - test('takes parameters for the query', () => { - pickFruit('blueberry', 20, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'blueberry', - quantity: 20, - }); - }); - - test('returns synchronously', () => { - expect(pickFruit('melon', 1, () => 'PURCHASE')).toBe('PURCHASE'); - }); - - test('returns the inventory status', () => { - expect(pickFruit('melon', 1, () => 'NOOP')).toBe('NOOP'); - }); -}); - -describe('inventory result callback', () => { - test('throws error if receives inventory error', () => { - expect(() => { - purchaseInventoryIfAvailable('inventory error', undefined); - }).toThrow(); - }); - - test('returns "PURCHASE" when inventory is available', () => { - expect(purchaseInventoryIfAvailable(null, true)).toBe('PURCHASE'); - }); - - test('returns "NOOP" when inventory is unavailable', () => { - expect(purchaseInventoryIfAvailable(null, false)).toBe('NOOP'); - }); -}); - -describe('putting it together', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - setResponse(null, true); - pickAndPurchaseFruit('jackfruit', 15); - expect(getLastQuery()).toEqual({ - variety: 'jackfruit', - quantity: 15, - }); - }); - - test('takes parameters for the query', () => { - setResponse(null, true); - pickAndPurchaseFruit('raspberry', 30); - expect(getLastQuery()).toEqual({ - variety: 'raspberry', - quantity: 30, - }); - }); - - test('throws error if receives inventory error', () => { - expect(() => { - pickAndPurchaseFruit('honeydew', 3); - }).toThrow(); - }); - - test('returns "NOOP" if quantity not available', () => { - setResponse(null, false); - expect(pickAndPurchaseFruit('apples', 12)).toBe('NOOP'); - }); - - test('returns "PURCHASE" if quantity available', () => { - setResponse(null, true); - expect(pickAndPurchaseFruit('oranges', 22)).toBe('PURCHASE'); - }); -}); diff --git a/exercises/concept/fruit-picker/global.d.ts b/exercises/concept/fruit-picker/global.d.ts index 26535a644e..2405f0f106 100644 --- a/exercises/concept/fruit-picker/global.d.ts +++ b/exercises/concept/fruit-picker/global.d.ts @@ -4,26 +4,23 @@ * type information on the fly */ -type Status = 'OFFLINE' | 'ONLINE'; -type AvailabilityAction = 'NOOP' | 'PURCHASE'; +type GrocerQuery = { + fruit: string; + quantity: number; +}; -interface CheckStatus { - callback: StatusCallback; -} +type GrocerOnSuccess = (quantityOrdered: number) => unknown -type StatusCallback = (response: Status) => boolean; +type GrocerOnError = (errorMessage: string) => unknown -interface CheckInventory { - query: GrocerQuery; - callback: InventoryCallback; +type FruitPickerSuccess = { + message: 'SUCCESS' } -type GrocerQuery = { - variety: string; - quantity: number; -}; +type FruitPickerError = { + message: 'ERROR' +} + +type FruitPickerSuccessCallback = () => SuccessResult; -type InventoryCallback = ( - err: string | null, - isAvailable: boolean -) => AvailabilityAction; +type FruitPickerErrorCallback = () => ErrorResult; diff --git a/exercises/concept/fruit-picker/grocer.js b/exercises/concept/fruit-picker/grocer.js index 959dbeaf5f..7fdec2a8e8 100644 --- a/exercises/concept/fruit-picker/grocer.js +++ b/exercises/concept/fruit-picker/grocer.js @@ -1,70 +1,19 @@ /** - * STORE STATUS API + * STORE API */ -let storeStatus = 'OFFLINE'; - -/** - * For testing purposes, set the store status - * @param {string} status - */ -export function setStatus(status) { - storeStatus = status; -} - -/** - * For testing purposes, reset the store status - */ -export function resetStatus() { - storeStatus = 'OFFLINE'; -} - -/** - * Invokes the callback with the store's status to simulate an API call - * @param {StatusCallback} callback - */ -export function checkStatus(callback) { - return callback(storeStatus); -} - -/** - * INVENTORY API - */ - -let lastInventoryQuery = undefined; -let inventoryResponse = undefined; - -/** - * For testing purposes, set the response to return when queried - * @param {any} ...nextResponse - */ -export function setResponse(...nextResponse) { - inventoryResponse = nextResponse; -} - /** - * For testing purposes, get the last query - * @return {string} + * @param {GrocerQuery} query + * @param {GrocerOnSuccess} onSuccess + * @param {GrocerOnError} onError + * @return void */ -export function getLastQuery() { - return lastInventoryQuery; -} +export function order(query, onSuccess, onError) { + const quantityAvailable = Math.random() * query.quantityAvailable * 2 -/** - * For testing purposes, reset the last query - */ -export function resetQuery() { - lastInventoryQuery = undefined; - inventoryResponse = ['undefined response']; -} - -/** - * Checks the inventory (inventoryResponse) then invokes the callback with the result - * @param {GrocerQuery} query - * @param {InventoryCallback} callback - * @return {AvailabilityAction} return the result of the callback - */ -export function checkInventory(query, callback) { - lastInventoryQuery = query; - return callback.apply(null, inventoryResponse); + if (quantityAvailable >= query.quantity) { + onSuccess(query.quantity) + } else { + onError(`desired quantity of '${query.fruit}' is not available`) + } } From fc95fb64f1eee715c2cac22a3beaf0dc74eb5870 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Fri, 15 Apr 2022 18:32:11 -0600 Subject: [PATCH 02/16] iteration - use fake declared modules - all synchronous callbacks which do not expect return values --- .../concept/fruit-picker/.meta/config.json | 2 +- .../concept/fruit-picker/fruit-picker.js | 28 +++++++++------- exercises/concept/fruit-picker/global.d.ts | 32 ++++++++++++++----- exercises/concept/fruit-picker/grocer.js | 19 ----------- 4 files changed, 42 insertions(+), 39 deletions(-) delete mode 100644 exercises/concept/fruit-picker/grocer.js diff --git a/exercises/concept/fruit-picker/.meta/config.json b/exercises/concept/fruit-picker/.meta/config.json index b001431f38..8498d8d2bd 100644 --- a/exercises/concept/fruit-picker/.meta/config.json +++ b/exercises/concept/fruit-picker/.meta/config.json @@ -16,7 +16,7 @@ ".meta/exemplar.js" ], "editor": [ - "grocer.js" + "grocer.js", "notifier.js" ] }, "blurb": "Learn about callbacks picking fruit from the grocer", diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index ffb71c033d..ed828dd656 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -9,20 +9,21 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { order } from './grocer'; +import { notify } from 'notifier'; +import { order } from 'grocer'; /** - * @return {FruitPickerSuccess} + * @return void */ export function onSuccess() { - return { message: 'SUCCESS' } + notify({ message: 'SUCCESS' }); } /** - * @return {FruitPickerError} + * @return void */ export function onError() { - return { message: 'ERROR'} + notify({ message: 'ERROR' }); } /** @@ -31,10 +32,15 @@ export function onError() { * @param {FruitPickerErrorCallback} onError * @return void */ -export function orderFromGrocer( - query, - onSuccess, - onError -) { - order(query, onSuccess, onError) +export function orderFromGrocer(query, onSuccess, onError) { + order(query, onSuccess, onError); +} + +/** + * @param {string} variety + * @param {number} quantity + * @return void + */ +export function postOrder(variety, quantity) { + orderFromGrocer({ variety, quantity }, onSuccess, onError); } diff --git a/exercises/concept/fruit-picker/global.d.ts b/exercises/concept/fruit-picker/global.d.ts index 2405f0f106..19087979c6 100644 --- a/exercises/concept/fruit-picker/global.d.ts +++ b/exercises/concept/fruit-picker/global.d.ts @@ -4,21 +4,37 @@ * type information on the fly */ +type FruitPickerSuccess = { + message: 'SUCCESS'; +}; + +type FruitPickerError = { + message: 'ERROR'; +}; + +declare module 'notifier' { + function notify(message: FruitPickerSuccess | FruitPickerError): void; +} + type GrocerQuery = { - fruit: string; + variety: string; quantity: number; }; -type GrocerOnSuccess = (quantityOrdered: number) => unknown - -type GrocerOnError = (errorMessage: string) => unknown +interface GrocerOnSuccessCallback { + (quantityOrdered: number): void; +} -type FruitPickerSuccess = { - message: 'SUCCESS' +interface GrocerOnErrorCallback { + (errorMessage: string): void; } -type FruitPickerError = { - message: 'ERROR' +declare module 'grocer' { + function order( + query: GrocerQuery, + onSuccess: GrocerOnSuccessCallback, + onError: GrocerOnErrorCallback + ): void; } type FruitPickerSuccessCallback = () => SuccessResult; diff --git a/exercises/concept/fruit-picker/grocer.js b/exercises/concept/fruit-picker/grocer.js deleted file mode 100644 index 7fdec2a8e8..0000000000 --- a/exercises/concept/fruit-picker/grocer.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * STORE API - */ - -/** - * @param {GrocerQuery} query - * @param {GrocerOnSuccess} onSuccess - * @param {GrocerOnError} onError - * @return void - */ -export function order(query, onSuccess, onError) { - const quantityAvailable = Math.random() * query.quantityAvailable * 2 - - if (quantityAvailable >= query.quantity) { - onSuccess(query.quantity) - } else { - onError(`desired quantity of '${query.fruit}' is not available`) - } -} From 16fc3faa25be866e02ad22f759c234d791b73d44 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Fri, 15 Apr 2022 19:23:13 -0600 Subject: [PATCH 03/16] update test file --- .../concept/fruit-picker/fruit-picker.js | 8 +-- .../concept/fruit-picker/fruit-picker.spec.js | 67 ++++++++++++++++--- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index ed828dd656..b9664034cf 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -28,12 +28,12 @@ export function onError() { /** * @param {GrocerQuery} query - * @param {FruitPickerSuccessCallback} onSuccess - * @param {FruitPickerErrorCallback} onError + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback * @return void */ -export function orderFromGrocer(query, onSuccess, onError) { - order(query, onSuccess, onError); +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + order(query, onSuccessCallback, onErrorCallback); } /** diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index 8a2b28b6f3..564e73e6d2 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -1,10 +1,61 @@ // @ts-check -import { - onError, - onSuccess, - orderFromGrocer -} from './fruit-picker'; -import { - order -} from './grocer'; +// eslint-disable-next-line import/no-unresolved +import { notify } from 'notifier'; +// eslint-disable-next-line import/no-unresolved +import { order } from 'grocer'; +import { onError, onSuccess, orderFromGrocer, postOrder } from './fruit-picker'; + +jest.mock( + 'notifier', + () => ({ + notify: jest.fn(), + }), + { virtual: true } +); + +jest.mock( + 'grocer', + () => ({ + order: jest.fn(), + }), + { virtual: true } +); + +describe('fruit-picker', () => { + afterEach(() => { + jest.resetAllMocks(); + }); + + test('onSuccess should call notify with a success message', () => { + expect(onSuccess()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'SUCCESS' }); + }); + + test('onError should call notify with an error message', () => { + expect(onError()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'ERROR' }); + }); + + test('order from grocer passes callback arguments forward to order', () => { + const query = { variety: 'apple', quantity: 10 }; + orderFromGrocer(query, onSuccess, onError); + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith(query, onSuccess, onError); + }); + + test('postOrder composes a request to the grocer using the defined callbacks', () => { + const variety = 'banana'; + const quantity = 5; + postOrder(variety, quantity); + + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith( + { variety, quantity }, + onSuccess, + onError + ); + }); +}); From f6e330af920af912cbfc8780ed5ec58057dd529f Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Fri, 15 Apr 2022 20:01:14 -0600 Subject: [PATCH 04/16] update exemplar and stubify exercise --- .../concept/fruit-picker/.meta/exemplar.js | 46 +++++++++++++++++++ .../concept/fruit-picker/fruit-picker.js | 8 ++-- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index e69de29bb2..3e3c613fa2 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -0,0 +1,46 @@ +/// +// +// @ts-check +// +// The lines above enable type checking for this file. Various IDEs interpret +// the @ts-check and reference directives. Together, they give you helpful +// autocompletion when implementing this exercise. You don't need to understand +// them in order to use it. +// +// In your own projects, files, and code, you can play with @ts-check as well. + +import { notify } from 'notifier'; +import { order } from 'grocer'; + +/** + * @return void + */ +export function onSuccess() { + notify({ message: 'SUCCESS' }); +} + +/** + * @return void + */ +export function onError() { + notify({ message: 'ERROR' }); +} + +/** + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback + * @return void + */ +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + order(query, onSuccessCallback, onErrorCallback); +} + +/** + * @param {string} variety + * @param {number} quantity + * @return void + */ +export function postOrder(variety, quantity) { + orderFromGrocer({ variety, quantity }, onSuccess, onError); +} diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index b9664034cf..ae5b3f336a 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -16,14 +16,14 @@ import { order } from 'grocer'; * @return void */ export function onSuccess() { - notify({ message: 'SUCCESS' }); + // implement the onSuccess callback to call notify with a success message } /** * @return void */ export function onError() { - notify({ message: 'ERROR' }); + // implement the onError callback to call notify with an error message } /** @@ -33,7 +33,7 @@ export function onError() { * @return void */ export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { - order(query, onSuccessCallback, onErrorCallback); + // implement the orderFromGrocer function to order the query } /** @@ -42,5 +42,5 @@ export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { * @return void */ export function postOrder(variety, quantity) { - orderFromGrocer({ variety, quantity }, onSuccess, onError); + //implement the postOrder function to create a query and order } From 90f01703d4dfdeb9fe2d42426cf303694131345a Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Fri, 15 Apr 2022 21:28:00 -0600 Subject: [PATCH 05/16] update some of the docs to reduce scope --- .../fruit-picker/.docs/instructions.md | 57 +++++++------------ .../fruit-picker/.docs/introduction.md | 40 +------------ .../concept/fruit-picker/.meta/config.json | 26 +++------ .../concept/fruit-picker/.meta/design.md | 2 - 4 files changed, 29 insertions(+), 96 deletions(-) diff --git a/exercises/concept/fruit-picker/.docs/instructions.md b/exercises/concept/fruit-picker/.docs/instructions.md index 99a36741ce..c38ba13bdd 100644 --- a/exercises/concept/fruit-picker/.docs/instructions.md +++ b/exercises/concept/fruit-picker/.docs/instructions.md @@ -2,21 +2,27 @@ You are creating a new online portal for your patrons to order their fruit fresh from the grocer. The grocer has an API that you can use to see if they have the inventory desired by your customers. You need to create a small library of functions for interacting with the grocer's API. -## 1. Check if the grocer's service is online +## 1. Create a callback to be called when the order is successful -The grocer's application programming interface [API] provides a function to check if their service is online called `checkStatus`. Use the grocer's function to finish the implementation of your `isServiceOnline` function. The `checkStatus` function takes a callback function to receive the status from the API. +Write a callback function called `onSuccess` to be called when the order is successful. It should invoke the imported `notify` function passing a success message to it. -- if the status is `'ONLINE'`, return `true` -- if the status is `'OFFLINE'`, return `false` +```javascript +onSuccess(); +// => `nofify` called with `{ message: 'SUCCESS' }` +``` + +## 2. Create a callback to be called when the order fails with an error + +Write a callback function called `onError` to be called when the order encounters an error. It should invoke the imported `notify` function passing an error message to it. ```javascript -isServiceOnline(); -// => true or false +onError(); +// => `nofify` called with `{ message: 'ERROR' }` ``` -## 2. See if the grocer has some fruit +## 3. Create a wrapper to wrap the external api function -The grocer's API provides a function to query their inventory called `checkInventory`. It receives two arguments: a _query_, and a _callback_ function. +The grocer's API provides a function to order from their inventory called `order`. It receives three arguments: a _query_, a _callback_ function to be invoked when the order is successful, and a _callback_ function to be invoked when the order encounters an error. You decide to wrap the api function call in a newly defined function `orderFromGrocer` to insulate your codebase from external changes. Your function should forward the arguments (which match the provided api function) to the api function. The query takes the form of an _object_: @@ -27,39 +33,16 @@ const query = { }; ``` -For your `pickFruit` function, you have decided to generalize it and just pass along a callback. So using the arguments `variety` and `quantity` finish the function to call the `checkInventory` API. - -```javascript -function action(err, data) { - // logic -} - -pickFruit('pineapple', 20, action); -// calls the checkInventory function with the query and passes along the `action` callback function -``` - -## 3. Create a callback to buy fruit if the inventory is available - -Finish the `purchaseInventoryIfAvailable` callback function to be used with the grocer's `checkInventory` API function. The API function expects callback functions to accept two arguments, `err` and `isAvailable`. If an error occurs when checking the inventory, a string is returned to `err`. If there is no error, the value is `null`. `isAvailable` is a _boolean_ value, but if there is an error it is _undefined_. - -To finish `purchaseInventoryIfAvailable`, throw a new error if `err` is not null. Otherwise, return `'PURCHASE'` if `isAvailable` is _true_ or `'NOOP'` if _false_. - ```javascript -purchaseInventoryIfAvailable('Server Offline', undefined); -// => Throws new error "Server Offline" - -purchaseInventoryIfAvailable(null, true); -// => 'PURCHASE' - -purchaseInventoryIfAvailable(null, false); -// => 'NOOP' +orderFromGrocer({variety: 'pear', quantity: 12}, exampleSuccessCallback, exampleErrorCallback) +// => `order` was called with the query and the callbacks ``` -## 4. Put it all together +## 4. Create a convenient short function -You notice that you're using `pickFruit` and `purchaseInventoryIfAvailable` so you decide to DRY up your code by extracting the code into a separate function called `pickAndPurchaseFruit`. Reuse `pickFruit` and `purchaseInventoryIfAvailable` to finish this function. +You find that you are calling this function from many different places with the same functions. Seeing an opportunity to refactor your code, you want to create a function where you can supply the variety and quantity to order as arguments. ```javascript -pickAndPurchaseFruit('Red Delicious Apples', 42); -// 'PURCHASE' if available or 'NOOP' if not available +postOrder('peach', 100) +// order submitted for 100 peaches ``` diff --git a/exercises/concept/fruit-picker/.docs/introduction.md b/exercises/concept/fruit-picker/.docs/introduction.md index 3769497d64..bb5ef31c6b 100644 --- a/exercises/concept/fruit-picker/.docs/introduction.md +++ b/exercises/concept/fruit-picker/.docs/introduction.md @@ -1,8 +1,8 @@ # Introduction -## Callbacks +## Callback functions -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +Callback functions are functions passed as arguments. This programming pattern facilitates creating a sequence of function calls in both synchronous and asynchronous contexts. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript const squareLength = 5; @@ -27,39 +27,3 @@ applyToSquare(function squarePerimeter(side) { return side * 4; }); ``` - -## Arrow Functions - -Besides function declarations and function expressions, JavaScript also has another very concise syntax for defining a function. -These functions are called _arrow functions_. - -Here is a comparison between a function declaration and an arrow function. - -```javascript -function addUpTwoNumbers(num1, num2) { - return num1 + num2; -} - -// function keyword removed and => added -const addUpTwoNumbers = (num1, num2) => { - return num1 + num2; -}; -``` - -If the function body contains only a return statement, like in the example above, the `{}` and the `return` keyword can be omitted. -If there is only one parameter, the parenthesis `()` can be omitted as well. - - -```javascript -const addUpTwoNumbers = (num1, num2) => num1 + num2; -const square = num => num * num; -``` - - -Arrow functions are often used to define short callback functions directly in the function call. - - -```javascript -applyToSquare(number => number * number); -``` - diff --git a/exercises/concept/fruit-picker/.meta/config.json b/exercises/concept/fruit-picker/.meta/config.json index 8498d8d2bd..ecba91b504 100644 --- a/exercises/concept/fruit-picker/.meta/config.json +++ b/exercises/concept/fruit-picker/.meta/config.json @@ -1,25 +1,13 @@ { - "authors": [ - "neenjaw" - ], - "contributors": [ - "SleeplessByte" - ], + "authors": ["neenjaw"], + "contributors": ["SleeplessByte"], "files": { - "solution": [ - "fruit-picker.js" - ], - "test": [ - "fruit-picker.spec.js" - ], - "exemplar": [ - ".meta/exemplar.js" - ], - "editor": [ - "grocer.js", "notifier.js" - ] + "solution": ["fruit-picker.js"], + "test": ["fruit-picker.spec.js"], + "exemplar": [".meta/exemplar.js"], + "editor": [] }, - "blurb": "Learn about callbacks picking fruit from the grocer", + "blurb": "Learn about callbacks ordering fruit from the grocer", "custom": { "version.tests.compatibility": "jest-27", "flag.tests.task-per-describe": true, diff --git a/exercises/concept/fruit-picker/.meta/design.md b/exercises/concept/fruit-picker/.meta/design.md index 3630cf7a31..9c16658f4f 100644 --- a/exercises/concept/fruit-picker/.meta/design.md +++ b/exercises/concept/fruit-picker/.meta/design.md @@ -11,7 +11,6 @@ In other words: how _function_ can be passed as an argument to another function, - Function that can pass along a callback function as an argument - How to write a function that can be used as a callback - How to compose functions with callbacks -- Functions using arrow functions ## Out of scope @@ -20,7 +19,6 @@ In other words: how _function_ can be passed as an argument to another function, ## Concepts -- `arrow-functions` - `callbacks` ## Prerequisites From 92138ec607ee62114b576fc72b1cd1df09aee370 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sun, 1 May 2022 21:08:16 -0600 Subject: [PATCH 06/16] More clean up: - introduction tidied - about tidied - created empty helper module function to satisfy eslint checks - removed arrow-function concept from taught concepts --- concepts/callbacks/about.md | 67 ++++--------------- concepts/callbacks/introduction.md | 12 +--- config.json | 2 +- .../fruit-picker/.docs/instructions.md | 2 +- .../fruit-picker/.docs/introduction.md | 2 +- .../concept/fruit-picker/.meta/config.json | 2 +- .../concept/fruit-picker/fruit-picker.js | 4 +- .../concept/fruit-picker/fruit-picker.spec.js | 22 ++---- exercises/concept/fruit-picker/grocer.js | 12 ++++ exercises/concept/fruit-picker/notifier.js | 8 +++ 10 files changed, 50 insertions(+), 83 deletions(-) create mode 100644 exercises/concept/fruit-picker/grocer.js create mode 100644 exercises/concept/fruit-picker/notifier.js diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index b5e0cc86c1..6733d56165 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -1,68 +1,28 @@ # About -[_Callbacks_ describe the pattern][wiki-callbacks] where a function receives a function as an argument to invoke when it arrives at a condition. The condition may be that its work is done, or that an event has occurred, or that some predicate passes. It can be synchronous; it can be asynchronous. +[_Callback_ functions][wiki-callbacks] are functions passed as arguments to other functions. The callback function may then be invoked to create a sequence of events. Often, _callbacks_ are used to handle the results of work done, or handle an action when an event occurs. _Callback_ functions can be used in synchronous and asynchronous programming. -This is a useful pattern in JavaScript because it is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. You might have noticed this on websites when they seem to "freeze" or become unresponsive. +This is a useful pattern in JavaScript because JavaScript is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. -But many API calls (often I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. +Many API calls (I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. -It is also useful when the `callback` (the argument passed in) may not be defined (created) at the calling site. In other words: it may have been passed down from a different place in the program. +It is also useful to use _callback functions_ because they may reference variables in its closure scope which are unavailable to the function where it is later invoked. -If the `callback` function _returns_ a boolean or boolean-like value, which is intended to be used (as opposed to a throwaway return value), it's called a predicate. +## Specific examples of callback functions -## Synchronous Code +### Event Handlers -A synchronous call is when one function is executed after the other. The order is fixed. +_Event handlers_ are a common use-case for callbacks in JavaScript. Browser events like `'onload'` or `'onclick'` are signals which can trigger functions to be invoked. A DOM [[Document Object Model](mdn-dom) object's `addEventListener` method registers a callback function to be invoked when it "hears" that an event has occurred. ```javascript -function triangleArea(height, base) { - return (height * base) / 2; -} - -triangleArea(2, 10); // => 10 -triangleArea(40, 3); // => 60 -``` - -## Asynchronous Code - -When an asynchronous function is invoked, there is no way to know for certain when it will finish its work. This means there is no value to act on when the function returns to the caller. - -```javascript -// This is broken, it may or may not return your value in time to be used -let area = asynchronousTriangleArea(4, 7); -console.log(area); -``` - -So we can use callbacks to control the order of execution: - -```javascript -function areaCallback(area) { - console.log(area); -} - -function asynchronousTriangleArea(height, base, areaCallback) { - areaCallback((height * base) / 2); -} - -// This outputs the area of the triangle to the console as expected. -asynchronousCallback(4, 7, areaCallback); -``` - -## Specific callback forms - -### Browser Events - -_Event handlers_ are a common use case for callbacks in JavaScript. This often takes the form of browser events like `'onload'` or `'onclick'`, where a DOM object's `addEventListener` method then registers a callback to be invoked when a specified event occurs. - -```javascript -document.addEventListener('onload' () => alert('The webpage has now been loaded')) +document.addEventListener('onload', () => alert('The webpage has now been loaded')) ``` -### Node.js Error Convention +### Node.js Convention -In [Node.js][nodejs], [callbacks][node-callbacks] often follow a [similar convention][node-error-convention] for their arguments: The first argument receives an `Error` object or `null` if no error occurred, and the second and subsequent receive the data that the calling function is designed to send. +In [Node.js][nodejs], [callback functions][node-callbacks] follow a [convention][node-error-convention] to control the flow of a program. They follow this pattern: the first argument of the callback function may receive an `Error` or `null`; The second and subsequent arguments receive the data that the calling function is designed to send. -If an error occurs, the second and subsequent arguments may not be present, so don't depend on them. +If an error occurs, the second and subsequent arguments may not be present, so you may not depend on them to be present. ```javascript function operation(a, b, callback) { @@ -78,7 +38,7 @@ function operation(a, b, callback) { function callback(error, returnedData) { if (error) { - // An error occured, handle it here. + // An error occurred, handle it here. return } @@ -88,11 +48,12 @@ function callback(error, returnedData) { operation(1, 2, callback) ``` -You see this pattern often when dealing with asynchronous functions. +You see this pattern often when dealing with asynchronous functions to assist with control flow. [mdn-callbacks]: https://developer.mozilla.org/en-US/docs/Glossary/Callback_function [mdn-concurrency-stack]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#stack [mdn-concurrency-queue]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#queue +[mdn-dom]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model [nodejs]: https://www.nodejs.org [node-callbacks]: https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/ [node-error-convention]: https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/ diff --git a/concepts/callbacks/introduction.md b/concepts/callbacks/introduction.md index 517142f15c..3a88419e35 100644 --- a/concepts/callbacks/introduction.md +++ b/concepts/callbacks/introduction.md @@ -1,6 +1,8 @@ # Introduction -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +## Callback functions + +Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript const squareLength = 5; @@ -25,11 +27,3 @@ applyToSquare(function squarePerimeter(side) { return side * 4; }); ``` - -Or an anonymous inline arrow function expression: - -```javascript -applyToSquare((side) => side * 4); -``` - -// The argument "(side) => side \* 4" is the callback diff --git a/config.json b/config.json index e9e0720e68..eee61c0859 100644 --- a/config.json +++ b/config.json @@ -181,7 +181,7 @@ "slug": "fruit-picker", "name": "Fruit Picker", "uuid": "a6348db8-cc2b-4c53-9f43-3c23248d66f0", - "concepts": ["callbacks", "arrow-functions"], + "concepts": ["callbacks"], "prerequisites": ["functions", "objects"], "status": "beta" }, diff --git a/exercises/concept/fruit-picker/.docs/instructions.md b/exercises/concept/fruit-picker/.docs/instructions.md index c38ba13bdd..6e572f8c07 100644 --- a/exercises/concept/fruit-picker/.docs/instructions.md +++ b/exercises/concept/fruit-picker/.docs/instructions.md @@ -44,5 +44,5 @@ You find that you are calling this function from many different places with the ```javascript postOrder('peach', 100) -// order submitted for 100 peaches +// => order submitted for 100 peaches ``` diff --git a/exercises/concept/fruit-picker/.docs/introduction.md b/exercises/concept/fruit-picker/.docs/introduction.md index bb5ef31c6b..3a88419e35 100644 --- a/exercises/concept/fruit-picker/.docs/introduction.md +++ b/exercises/concept/fruit-picker/.docs/introduction.md @@ -2,7 +2,7 @@ ## Callback functions -Callback functions are functions passed as arguments. This programming pattern facilitates creating a sequence of function calls in both synchronous and asynchronous contexts. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. +Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript const squareLength = 5; diff --git a/exercises/concept/fruit-picker/.meta/config.json b/exercises/concept/fruit-picker/.meta/config.json index ecba91b504..09dfe3950c 100644 --- a/exercises/concept/fruit-picker/.meta/config.json +++ b/exercises/concept/fruit-picker/.meta/config.json @@ -5,7 +5,7 @@ "solution": ["fruit-picker.js"], "test": ["fruit-picker.spec.js"], "exemplar": [".meta/exemplar.js"], - "editor": [] + "editor": ["notifier.js", "grocer.js"] }, "blurb": "Learn about callbacks ordering fruit from the grocer", "custom": { diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index ae5b3f336a..2390e6c336 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -9,8 +9,8 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { notify } from 'notifier'; -import { order } from 'grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** * @return void diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index 564e73e6d2..dd857443d1 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -6,21 +6,13 @@ import { notify } from 'notifier'; import { order } from 'grocer'; import { onError, onSuccess, orderFromGrocer, postOrder } from './fruit-picker'; -jest.mock( - 'notifier', - () => ({ - notify: jest.fn(), - }), - { virtual: true } -); - -jest.mock( - 'grocer', - () => ({ - order: jest.fn(), - }), - { virtual: true } -); +jest.mock('./notifier', () => ({ + notify: jest.fn(), +})); + +jest.mock('./grocer', () => ({ + order: jest.fn(), +})); describe('fruit-picker', () => { afterEach(() => { diff --git a/exercises/concept/fruit-picker/grocer.js b/exercises/concept/fruit-picker/grocer.js new file mode 100644 index 0000000000..f167889979 --- /dev/null +++ b/exercises/concept/fruit-picker/grocer.js @@ -0,0 +1,12 @@ +/** + * @param {GrocerQuery} query + * @param {GrocerOnSuccessCallback} onSuccess + * @param {GrocerOnErrorCallback} onError + * @return void + */ +export function order(query, onSuccess, onError) { + // This is a mocked function for use with the exercise. + query; + onSuccess; + onError; +} diff --git a/exercises/concept/fruit-picker/notifier.js b/exercises/concept/fruit-picker/notifier.js new file mode 100644 index 0000000000..b30eaca2d0 --- /dev/null +++ b/exercises/concept/fruit-picker/notifier.js @@ -0,0 +1,8 @@ +/** + * @param {FruitPickerSuccess | FruitPickerError} message + * @return void + */ +export function notify(message) { + // This is a mocked function for use with the exercise. + message; +} From ef58611dddb868090d192e8069005d0e332f20c1 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sun, 1 May 2022 21:10:49 -0600 Subject: [PATCH 07/16] configlet feedback --- config.json | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/config.json b/config.json index eee61c0859..f82c6416d0 100644 --- a/config.json +++ b/config.json @@ -109,13 +109,7 @@ "name": "Elyses Analytic Enchantments", "uuid": "45d956db-d4ef-4468-b1d3-47021f172c15", "concepts": ["array-analysis"], - "prerequisites": [ - "arrays", - "booleans", - "callbacks", - "arrow-functions", - "numbers" - ], + "prerequisites": ["arrays", "booleans", "callbacks", "numbers"], "status": "beta" }, { @@ -131,13 +125,7 @@ "name": "Elyses Looping Enchantments", "uuid": "e06f8f70-019f-4cec-924b-3971414e15d9", "concepts": ["array-loops"], - "prerequisites": [ - "arrays", - "callbacks", - "arrow-functions", - "for-loops", - "conditionals" - ], + "prerequisites": ["arrays", "callbacks", "for-loops", "conditionals"], "status": "beta" }, { @@ -190,7 +178,7 @@ "name": "Translation Service", "uuid": "4a967656-8615-474e-a009-5c0b09f4386f", "concepts": ["promises"], - "prerequisites": ["callbacks", "arrow-functions", "errors"], + "prerequisites": ["callbacks", "errors"], "status": "beta" }, { @@ -237,13 +225,7 @@ "name": "Elyses Transformative Enchantments", "uuid": "6e156d67-2bd2-4624-956d-ddcc3795bad5", "concepts": ["array-transformations"], - "prerequisites": [ - "numbers", - "arrays", - "conditionals", - "callbacks", - "arrow-functions" - ], + "prerequisites": ["numbers", "arrays", "conditionals", "callbacks"], "status": "beta" }, { @@ -502,7 +484,6 @@ "prerequisites": [ "strings", "objects", - "arrow-functions", "regular-expressions", "errors" ], @@ -1315,7 +1296,6 @@ "prerequisites": [ "arrays", "array-transformations", - "arrow-functions", "errors", "for-loops", "rest-and-spread" From 3d7b81aa91162a4d7794505d9f4b67a45312f49f Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sun, 1 May 2022 21:12:28 -0600 Subject: [PATCH 08/16] update import in test file --- exercises/concept/fruit-picker/.meta/exemplar.js | 4 ++-- exercises/concept/fruit-picker/fruit-picker.spec.js | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index 3e3c613fa2..05b31e8dd8 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -9,8 +9,8 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { notify } from 'notifier'; -import { order } from 'grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** * @return void diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index dd857443d1..e43bacde05 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -1,9 +1,7 @@ // @ts-check -// eslint-disable-next-line import/no-unresolved -import { notify } from 'notifier'; -// eslint-disable-next-line import/no-unresolved -import { order } from 'grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; import { onError, onSuccess, orderFromGrocer, postOrder } from './fruit-picker'; jest.mock('./notifier', () => ({ From fe60906af3a02eb03bf8a874df765ab80725a28b Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sun, 1 May 2022 21:17:57 -0600 Subject: [PATCH 09/16] `npx prettier -w` --- concepts/callbacks/about.md | 4 +++- .../concept/fruit-picker/.docs/instructions.md | 16 ++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index 6733d56165..ddfa7382c1 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -15,7 +15,9 @@ It is also useful to use _callback functions_ because they may reference variabl _Event handlers_ are a common use-case for callbacks in JavaScript. Browser events like `'onload'` or `'onclick'` are signals which can trigger functions to be invoked. A DOM [[Document Object Model](mdn-dom) object's `addEventListener` method registers a callback function to be invoked when it "hears" that an event has occurred. ```javascript -document.addEventListener('onload', () => alert('The webpage has now been loaded')) +document.addEventListener('onload', function () { + alert('The webpage has now been loaded'); +}); ``` ### Node.js Convention diff --git a/exercises/concept/fruit-picker/.docs/instructions.md b/exercises/concept/fruit-picker/.docs/instructions.md index 6e572f8c07..9c41e710c7 100644 --- a/exercises/concept/fruit-picker/.docs/instructions.md +++ b/exercises/concept/fruit-picker/.docs/instructions.md @@ -4,20 +4,20 @@ You are creating a new online portal for your patrons to order their fruit fresh ## 1. Create a callback to be called when the order is successful -Write a callback function called `onSuccess` to be called when the order is successful. It should invoke the imported `notify` function passing a success message to it. +Write a callback function called `onSuccess` to be called when the order is successful. It should invoke the imported `notify` function passing a success message to it. ```javascript onSuccess(); -// => `nofify` called with `{ message: 'SUCCESS' }` +// => `notify` called with `{ message: 'SUCCESS' }` ``` ## 2. Create a callback to be called when the order fails with an error -Write a callback function called `onError` to be called when the order encounters an error. It should invoke the imported `notify` function passing an error message to it. +Write a callback function called `onError` to be called when the order encounters an error. It should invoke the imported `notify` function passing an error message to it. ```javascript onError(); -// => `nofify` called with `{ message: 'ERROR' }` +// => `notify` called with `{ message: 'ERROR' }` ``` ## 3. Create a wrapper to wrap the external api function @@ -34,7 +34,11 @@ const query = { ``` ```javascript -orderFromGrocer({variety: 'pear', quantity: 12}, exampleSuccessCallback, exampleErrorCallback) +orderFromGrocer( + { variety: 'pear', quantity: 12 }, + exampleSuccessCallback, + exampleErrorCallback +); // => `order` was called with the query and the callbacks ``` @@ -43,6 +47,6 @@ orderFromGrocer({variety: 'pear', quantity: 12}, exampleSuccessCallback, example You find that you are calling this function from many different places with the same functions. Seeing an opportunity to refactor your code, you want to create a function where you can supply the variety and quantity to order as arguments. ```javascript -postOrder('peach', 100) +postOrder('peach', 100); // => order submitted for 100 peaches ``` From 9538bc5dc0412bb4a00f5d5eb71e8a9eb7eaf297 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:13:25 -0600 Subject: [PATCH 10/16] remove ts-check explanation --- exercises/concept/fruit-picker/.meta/exemplar.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index 05b31e8dd8..dba3e028eb 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -1,13 +1,6 @@ /// // // @ts-check -// -// The lines above enable type checking for this file. Various IDEs interpret -// the @ts-check and reference directives. Together, they give you helpful -// autocompletion when implementing this exercise. You don't need to understand -// them in order to use it. -// -// In your own projects, files, and code, you can play with @ts-check as well. import { notify } from './notifier'; import { order } from './grocer'; From ea513fc44ca46d81fde40ae3c0661213dae4dea6 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:13:48 -0600 Subject: [PATCH 11/16] migrate to one describe per task --- .../concept/fruit-picker/fruit-picker.spec.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index e43bacde05..f920705511 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -12,30 +12,36 @@ jest.mock('./grocer', () => ({ order: jest.fn(), })); -describe('fruit-picker', () => { - afterEach(() => { - jest.resetAllMocks(); - }); +afterEach(() => { + jest.resetAllMocks(); +}); +describe('task 1', () => { test('onSuccess should call notify with a success message', () => { expect(onSuccess()).toEqual(undefined); expect(notify).toHaveBeenCalledTimes(1); expect(notify).toHaveBeenCalledWith({ message: 'SUCCESS' }); }); +}); +describe('task 2', () => { test('onError should call notify with an error message', () => { expect(onError()).toEqual(undefined); expect(notify).toHaveBeenCalledTimes(1); expect(notify).toHaveBeenCalledWith({ message: 'ERROR' }); }); +}); - test('order from grocer passes callback arguments forward to order', () => { +describe('task 3', () => { + test('order from grocer passes callback function arguments forward', () => { const query = { variety: 'apple', quantity: 10 }; orderFromGrocer(query, onSuccess, onError); expect(order).toHaveBeenCalledTimes(1); expect(order).toHaveBeenCalledWith(query, onSuccess, onError); }); +}); +describe('task 4', () => { test('postOrder composes a request to the grocer using the defined callbacks', () => { const variety = 'banana'; const quantity = 5; From ff230366db5d185188a26c6db4e72f6c91834291 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:14:36 -0600 Subject: [PATCH 12/16] re-introduce arrow-functions as taught-concept to be addressed later as they are not explicitly taught in the context of this exercise. --- config.json | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/config.json b/config.json index f82c6416d0..eb211ec953 100644 --- a/config.json +++ b/config.json @@ -109,7 +109,13 @@ "name": "Elyses Analytic Enchantments", "uuid": "45d956db-d4ef-4468-b1d3-47021f172c15", "concepts": ["array-analysis"], - "prerequisites": ["arrays", "booleans", "callbacks", "numbers"], + "prerequisites": [ + "arrays", + "arrow-functions", + "booleans", + "callbacks", + "numbers" + ], "status": "beta" }, { @@ -125,7 +131,13 @@ "name": "Elyses Looping Enchantments", "uuid": "e06f8f70-019f-4cec-924b-3971414e15d9", "concepts": ["array-loops"], - "prerequisites": ["arrays", "callbacks", "for-loops", "conditionals"], + "prerequisites": [ + "arrays", + "arrow-functions", + "callbacks", + "for-loops", + "conditionals" + ], "status": "beta" }, { @@ -169,7 +181,7 @@ "slug": "fruit-picker", "name": "Fruit Picker", "uuid": "a6348db8-cc2b-4c53-9f43-3c23248d66f0", - "concepts": ["callbacks"], + "concepts": ["arrow-functions", "callbacks"], "prerequisites": ["functions", "objects"], "status": "beta" }, @@ -225,7 +237,13 @@ "name": "Elyses Transformative Enchantments", "uuid": "6e156d67-2bd2-4624-956d-ddcc3795bad5", "concepts": ["array-transformations"], - "prerequisites": ["numbers", "arrays", "conditionals", "callbacks"], + "prerequisites": [ + "arrow-functions", + "numbers", + "arrays", + "conditionals", + "callbacks" + ], "status": "beta" }, { @@ -482,6 +500,7 @@ "uuid": "9131bdb8-2e0f-4526-b113-8a77712e7216", "practices": [], "prerequisites": [ + "arrow-functions", "strings", "objects", "regular-expressions", @@ -1296,6 +1315,7 @@ "prerequisites": [ "arrays", "array-transformations", + "arrow-functions", "errors", "for-loops", "rest-and-spread" From 7485701ec5807d84f9c1b7d383316e549f1a99f2 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:14:59 -0600 Subject: [PATCH 13/16] update function naming inexample --- concepts/callbacks/introduction.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/concepts/callbacks/introduction.md b/concepts/callbacks/introduction.md index 3a88419e35..2054e4a908 100644 --- a/concepts/callbacks/introduction.md +++ b/concepts/callbacks/introduction.md @@ -5,16 +5,16 @@ Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript -const squareLength = 5; +const sideLength = 5; // Caller function takes a callback function -function applyToSquare(callback) { - return callback(squareLength); +function applySideLength(callback) { + return callback(sideLength); } // Callback must expect the possible argument from the calling function -function areaOfSquare(number) { - return number * number; +function squareArea(side) { + return side * side; } applyToSquare(areaOfSquare); // => 25 @@ -23,7 +23,7 @@ applyToSquare(areaOfSquare); // => 25 You may also write callbacks as a function expression: ```javascript -applyToSquare(function squarePerimeter(side) { +applyToSquare(function squarePerimeterLength(side) { return side * 4; }); ``` From dd155a509988c5619d8e65a19e1f6686ce9a2c75 Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:44:45 -0600 Subject: [PATCH 14/16] updates to about.md - added basic example to about - added detail about array prototype function which use callbacks --- concepts/callbacks/about.md | 60 +++++++++++++++++++++++++++++- concepts/callbacks/introduction.md | 4 +- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index ddfa7382c1..f4f0b23c4e 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -1,6 +1,30 @@ # About -[_Callback_ functions][wiki-callbacks] are functions passed as arguments to other functions. The callback function may then be invoked to create a sequence of events. Often, _callbacks_ are used to handle the results of work done, or handle an action when an event occurs. _Callback_ functions can be used in synchronous and asynchronous programming. +[_Callback_ functions][wiki-callbacks] are functions passed as arguments to other functions. The callback function may then be invoked to trigger a subsequent action. Often, _callbacks_ are used to handle the results of work done, or handle an action when an event occurs. _Callback_ functions can be used in synchronous and asynchronous programming. + +```javascript +const sideLength = 5; + +// Caller function takes a callback function +function applySideLength(callback) { + return callback(sideLength); +} + +// Callback must expect the possible argument from the calling function +function squareArea(side) { + return side * side; +} + +applySideLength(areaOfSquare); // => 25 +``` + +You may also write callbacks as a function expression: + +```javascript +applySideLength(function squarePerimeter(side) { + return side * 4; +}); +``` This is a useful pattern in JavaScript because JavaScript is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. @@ -52,6 +76,40 @@ operation(1, 2, callback) You see this pattern often when dealing with asynchronous functions to assist with control flow. +### Callbacks in disguise + +Common `Array` functions use callback functions to define their behaviour: + +- `Array.prototype.forEach`: + - Accepts a callback, which applies the callback to each element of an array. + + ```javascript + [1, 2, 3].forEach(function (element) { + doSomething(element) + }); + // => doSomething() is invoked 3 times, once with each element + ``` + +- `Array.prototype.map` + - Accepts a callback, which applies the callback to each element of an array using the result to create a new array. + + ```javascript + [1,2,3].map(function (element) { + return element + 1; + }); + // => [2, 3, 4] + ``` + +- `Array.prototype.reduce` + - Accepts a callback, which applies the callback to each element of an array, passing the result forward to the next invocation. + + ```javascript + [1,2,3].reduce(function (runningSum, element) { + return runningSum + element; + }, 0); + // => 6 + ``` + [mdn-callbacks]: https://developer.mozilla.org/en-US/docs/Glossary/Callback_function [mdn-concurrency-stack]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#stack [mdn-concurrency-queue]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#queue diff --git a/concepts/callbacks/introduction.md b/concepts/callbacks/introduction.md index 2054e4a908..55defec5a2 100644 --- a/concepts/callbacks/introduction.md +++ b/concepts/callbacks/introduction.md @@ -17,13 +17,13 @@ function squareArea(side) { return side * side; } -applyToSquare(areaOfSquare); // => 25 +applySideLength(areaOfSquare); // => 25 ``` You may also write callbacks as a function expression: ```javascript -applyToSquare(function squarePerimeterLength(side) { +applySideLength(function squarePerimeterLength(side) { return side * 4; }); ``` From 06ae3bc3b5daaf638f5dd795c74c6a3f5409d24a Mon Sep 17 00:00:00 2001 From: Tim Austin Date: Sat, 25 Jun 2022 13:47:54 -0600 Subject: [PATCH 15/16] npx prettier --- concepts/callbacks/about.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index f4f0b23c4e..4573cdd86b 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -81,30 +81,33 @@ You see this pattern often when dealing with asynchronous functions to assist wi Common `Array` functions use callback functions to define their behaviour: - `Array.prototype.forEach`: + - Accepts a callback, which applies the callback to each element of an array. ```javascript [1, 2, 3].forEach(function (element) { - doSomething(element) + doSomething(element); }); // => doSomething() is invoked 3 times, once with each element ``` - `Array.prototype.map` + - Accepts a callback, which applies the callback to each element of an array using the result to create a new array. ```javascript - [1,2,3].map(function (element) { + [1, 2, 3].map(function (element) { return element + 1; }); // => [2, 3, 4] ``` - `Array.prototype.reduce` + - Accepts a callback, which applies the callback to each element of an array, passing the result forward to the next invocation. ```javascript - [1,2,3].reduce(function (runningSum, element) { + [1, 2, 3].reduce(function (runningSum, element) { return runningSum + element; }, 0); // => 6 From 0fe22a5033bd8a9b708a0ee21346806f9b2ef9c7 Mon Sep 17 00:00:00 2001 From: June <12543047+junedev@users.noreply.github.com> Date: Wed, 29 Jun 2022 21:57:26 +0200 Subject: [PATCH 16/16] Re-add arrow functions for Translation Service --- config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.json b/config.json index eb211ec953..922d815b1f 100644 --- a/config.json +++ b/config.json @@ -190,7 +190,7 @@ "name": "Translation Service", "uuid": "4a967656-8615-474e-a009-5c0b09f4386f", "concepts": ["promises"], - "prerequisites": ["callbacks", "errors"], + "prerequisites": ["callbacks", "arrow-functions", "errors"], "status": "beta" }, {