Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Fruit Picker -- remove conceptual leaps #1725

Merged
merged 16 commits into from
Jun 29, 2022
110 changes: 67 additions & 43 deletions concepts/callbacks/about.md
Original file line number Diff line number Diff line change
@@ -1,68 +1,54 @@
# 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 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.

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.

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.

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.

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.

## Synchronous Code
```javascript
const sideLength = 5;

A synchronous call is when one function is executed after the other. The order is fixed.
// Caller function takes a callback function
function applySideLength(callback) {
return callback(sideLength);
}

```javascript
function triangleArea(height, base) {
return (height * base) / 2;
// Callback must expect the possible argument from the calling function
function squareArea(side) {
return side * side;
}

triangleArea(2, 10); // => 10
triangleArea(40, 3); // => 60
applySideLength(areaOfSquare); // => 25
```

## 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.
You may also write callbacks as a function expression:

```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);
applySideLength(function squarePerimeter(side) {
return side * 4;
});
```

So we can use callbacks to control the order of execution:

```javascript
function areaCallback(area) {
console.log(area);
}
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.

function asynchronousTriangleArea(height, base, areaCallback) {
areaCallback((height * base) / 2);
}
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.

// This outputs the area of the triangle to the console as expected.
asynchronousCallback(4, 7, areaCallback);
```
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.

## Specific callback forms
## Specific examples of callback functions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having a the category for all the forEach, map, filter, etc methods in JavaScript? I often notice beginners don't understand that the argument is a callback function that will be applied to the elements. I think it would be good to include it as a first example category in the list. However, I am not sure how to name the category ... maybe "Iteration Callbacks" ... idk ... maybe you have a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aren't those array methods taught in other concept exercises? Shouldn't it be those exercises' responsibility to build on prior knowledge and point out that those array functions are callbacks in disguise?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We (DJ and me) have the guideline that about.md files are not bound by the syllabus progression because they are used for later reference. So if something is a good fit to explain a concept, we would include it in the about.md no matter where it would show up in the concept tree (if at all). My feeling is that the current example categories have some "overweight" on the complex side. That is why I though a simpler category would balance it out. Not a blocker though. Feel free to click resolve if you prefer it as is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detail added in dd155a5, sub-titled as "Callbacks in disguise"


### Browser Events
### Event Handlers

_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.
_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 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) {
Expand All @@ -78,7 +64,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
}

Expand All @@ -88,11 +74,49 @@ 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.

### 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
[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/
Expand Down
26 changes: 10 additions & 16 deletions concepts/callbacks/introduction.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
# 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;
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
applySideLength(areaOfSquare); // => 25
```

You may also write callbacks as a function expression:

```javascript
applyToSquare(function squarePerimeter(side) {
applySideLength(function squarePerimeterLength(side) {
return side * 4;
});
```

Or an anonymous inline arrow function expression:

```javascript
applyToSquare((side) => side * 4);
```

// The argument "(side) => side \* 4" is the callback
12 changes: 6 additions & 6 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@
"concepts": ["array-analysis"],
"prerequisites": [
"arrays",
"arrow-functions",
"booleans",
"callbacks",
"arrow-functions",
"numbers"
],
"status": "beta"
Expand All @@ -133,8 +133,8 @@
"concepts": ["array-loops"],
"prerequisites": [
"arrays",
"callbacks",
"arrow-functions",
"callbacks",
"for-loops",
"conditionals"
],
Expand Down Expand Up @@ -181,7 +181,7 @@
"slug": "fruit-picker",
"name": "Fruit Picker",
"uuid": "a6348db8-cc2b-4c53-9f43-3c23248d66f0",
"concepts": ["callbacks", "arrow-functions"],
"concepts": ["arrow-functions", "callbacks"],
"prerequisites": ["functions", "objects"],
"status": "beta"
},
Expand Down Expand Up @@ -238,11 +238,11 @@
"uuid": "6e156d67-2bd2-4624-956d-ddcc3795bad5",
"concepts": ["array-transformations"],
"prerequisites": [
"arrow-functions",
"numbers",
"arrays",
"conditionals",
"callbacks",
"arrow-functions"
"callbacks"
],
"status": "beta"
},
Expand Down Expand Up @@ -500,9 +500,9 @@
"uuid": "9131bdb8-2e0f-4526-b113-8a77712e7216",
"practices": [],
"prerequisites": [
"arrow-functions",
"strings",
"objects",
"arrow-functions",
"regular-expressions",
"errors"
],
Expand Down
61 changes: 24 additions & 37 deletions exercises/concept/fruit-picker/.docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();
// => `notify` called with `{ message: 'SUCCESS' }`
```

## 2. Create a callback to be called when the order fails with an error
neenjaw marked this conversation as resolved.
Show resolved Hide resolved

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();
// => `notify` 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_:

Expand All @@ -27,39 +33,20 @@ 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
```
40 changes: 2 additions & 38 deletions exercises/concept/fruit-picker/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -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 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;
Expand All @@ -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.

<!-- prettier-ignore-start -->
```javascript
const addUpTwoNumbers = (num1, num2) => num1 + num2;
const square = num => num * num;
```
<!-- prettier-ignore-end -->

Arrow functions are often used to define short callback functions directly in the function call.

<!-- prettier-ignore-start -->
```javascript
applyToSquare(number => number * number);
```
<!-- prettier-ignore-end -->
Loading