General instructions how to setup your computer and work on the assignments can be found here:
After cloning this repository to your local computer, install the dependencies with the following command:
npm install
The assignment for this week can be found in the assignment
folder.
To run the exercises in Node.js, cd
into the assignment
folder and use the following command:
node <exercise-file-name>.js
For example, to run the first exercise, use:
node ex1.js
Tip: You can use autocompletion in your terminal to quickly find the exercise files. Start typing
node ex1
and then press the Tab key to let the terminal complete the file name with.js
for you.
To ensure your code works as expected, we have provided unit tests for each exercise. You can run these tests using Jest, which is already set up in the project.
npx jest <exercise-file-name>
For example, to run the unit test for the first exercise, use:
npx jest ex1
To run all unit tests at once, you can simply use:
npx jest
Or, alternatively, to run all tests you can use the npm
command:
npm test
The unit tests will check if your implementation meets the requirements specified in the exercise description. If all tests pass, you can be confident that your solution is functionally correct.
These same unit tests will be used to evaluate your submission, so make sure they pass before submitting your assignment.
- The latest version of the
main
branch is the final version of your assignment solution. - You may commit and push code as many times as you want.
- You may use extra branches if you want. But don't forget to merge them to
main
.
The individual exercises will be described in the sections below.
Take a look at the following function (and try it out in your console):
export const getAnonName = (firstName, callback) => {
setTimeout(() => {
if (!firstName) {
callback(new Error("You didn't pass in a first name!"));
return;
}
const fullName = `${firstName} Doe`;
callback(fullName);
}, 0);
};
function main() {
getAnonName('John', console.log);
}
Rewrite this function, but replace the callback syntax with the Promise syntax:
- Have the
getAnonName
arrow function return anew Promise
. - If the Promise resolves, pass the full name as an argument to
resolve()
. - If the Promise rejects, pass an
Error
object containing "You didn't pass in a first name!" toreject()
.
Complete the function called checkDoubleDigits
such that:
- It takes one argument: a number
- It returns a
new Promise
. - If the number is between 10 and 99 it should resolve to the string "This is a double digit number!".
- For any other number it should reject with an Error object containing: "Expected a double digit number but got
number
", wherenumber
is the number that was passed as an argument.
This exercise is about throwing a die. A die in this exercise may roll up to 10 times before it settles on a final value, depending on the "force" with which it is thrown. Unfortunately, if a die rolls more than six times in our game it rolls off the table and the throw becomes invalid. If it rolls six times or less, its final ("settled") value will be valid.
Note: to keep things simple, we have taken some liberties in this exercise with respect to how a die behaves in reality. For instance, in real life a die cannot flip back to a value it previously had. And it will mostly roll on its corners, not its sides.
The existing rollDie()
function in the exercise file uses a callback to notify the caller of success or failure. Here is the code:
export function rollDie(callback) {
// Compute a random number of rolls (3-10) that the die MUST complete
const randomRollsToDo = Math.floor(Math.random() * 8) + 3;
console.log(`Die scheduled for ${randomRollsToDo} rolls...`);
const rollOnce = (roll) => {
// Compute a random die value for the current roll
const value = Math.floor(Math.random() * 6) + 1;
console.log(`Die value is now: ${value}`);
// Use callback to notify that the die rolled off the table after 6 rolls
if (roll > 6) {
// TODO replace "error" callback
callback(new Error('Oops... Die rolled off the table.'));
}
// Use callback to communicate the final die value once finished rolling
if (roll === randomRollsToDo) {
// TODO replace "success" callback
callback(null, value);
}
// Schedule the next roll to do until no more rolls to do
if (roll < randomRollsToDo) {
setTimeout(() => rollOnce(roll + 1), 500);
}
};
// Start the initial roll
rollOnce(1);
}
function main() {
// TODO Refactor to use promise
rollDie((error, value) => {
if (error !== null) {
console.log(error.message);
} else {
console.log(`Success! Die settled on ${value}.`);
}
});
}
A couple of comments about this code:
- In real life, a die, when thrown, will autonomously run its course until it comes to a complete standstill, abiding the laws of nature. How long it will roll depends on the force of the throw. In our simulation that "force" is represented by the random value assigned to
randomRollsToDo
. As if subjected to the laws of nature, we insist that our simulated dice continue to roll until they have reached their randomly assigned number of rolls-to-do, even after dropping off the table.- The "error first" callback format used in this example, using two parameters, is commonly used in Node.js. To communicate back failure, the callback is called with a single argument: the error value (usually a JavaScript
Error
object). In the successful case the callback is called with two arguments, the first one beingnull
(i.e., no error) and the second one containing the actual result.
Here is what the output could look like for a successful throw:
❯ node .\ex3-rollDie.js
Die scheduled for 5 rolls...
Die value is now: 2
Die value is now: 3
Die value is now: 4
Die value is now: 5
Die value is now: 6
Success! Die settled on 6.
However, there is a problem when the die rolls off the table. In that case we expect a single error callback and no "success" callbacks. Evidently this is not what we are getting in the random throw below.
❯ node .\ex3-rollDie.js
Die starts rolling...
Die scheduled for 8 rolls...
Die value is now: 1
Die value is now: 3
Die value is now: 3
Die value is now: 1
Die value is now: 2
Die value is now: 4
Oops... Die rolled off the table.
Die value is now: 5
Success! Die settled on 5.
Oops... Die rolled off the table
Since we want to practice with promises anyway, let's see what happens when we refactor the code to use promises:
- Run the unmodified program and confirm that the problem as described can be reproduced.
- TODO#1: Refactor the
rollDie()
function from using a callback to returning a promise. - TODO#2, TODO#3: Change the calls to
callback()
to calls toresolve()
andreject()
. - TODO#4: Refactor the code that calls
rollDie()
to use the returned promise. - TODO#5: Does the problem described above still occur? If not, what would be your explanation? Replace the placeholder text with your explanation.
Dice in a Poker Dice game have representations of playing cards upon them (this exercise uses strings instead). You play it with five such dice that you must throw in one go.
You do not need to understand the rules of the Poker Dice game in order to work on this exercise. The only thing that is important is that the dice can be thrown and each one will either settle on some value or "roll off the table".
In this exercise we have provided a ready-made rollDie()
function for you that takes a die number (1-5) as an argument and returns a promise that resolves to its final value, or a rejected promise with an Error
object if the die rolled off the table. The rollDie()
function is located in a separate file (pokerDiceRoller.js
). For this exercise you do not need to look at it, although you are welcome to do so. The only thing you need to know is that it returns a promise, as described above.
We have also provided some code that demonstrates how to handle throwing a single die. For this exercise you should do the following.
-
TODO#1: Refactor the
rollDice()
function to throw five dice in one go, by using.map()
on thedice
array to create an array of promises for use withPromise.all()
. -
A successful (i.e. resolved) throw should output a message similar to:
Resolved! [ 'JACK', 'QUEEN', 'QUEEN', 'NINE', 'JACK' ]
-
An unsuccessful (i.e. rejected) throw should output a message similar to:
Rejected! Die 3 rolled off the table.
The provided rollDie()
function logs the value of a die as it rolls, time-stamped with the time of day (with millisecond accuracy) to the console. Once you have successfully completed this exercise you will notice that the intermediate messages are output in bursts of up to five at a time as the dice finish rolling asynchronously. For example:
12:02:41.311 Die 1 scheduled for 3 rolls...
12:02:41.321 Die 1 is now: QUEEN
12:02:41.321 Die 2 scheduled for 6 rolls...
12:02:41.321 Die 2 is now: KING
12:02:41.321 Die 3 scheduled for 5 rolls...
...
12:02:42.315 Die 5 is now: NINE
12:02:42.316 Die 1 is now: JACK
12:02:42.316 Die 1 settles on JACK in 3 rolls.
12:02:42.320 Die 2 is now: TEN
...
TODO#2: You may also notice that, in the case of a rejected promise, dice that have not yet finished their roll continue to do so. Can you explain why? Please replace the placeholder text in the code with your explanation.
In the previous exercise we used Promise.all()
to throw five dice in one go. In the current exercise we will be throwing five dice one at a time, waiting for a die to settle before throwing the next one. Of course, we still consider a die rolling off the table to be a showstopper.
TODO#1: To throw the dice sequentially we will be using a promise chain. Your job is to expand the given promise chain to include five dice.