CS 347: Lab 13 – Node Shell
Dear students:
Welcome to lab. Now’s your chance to apply the ideas you read about. Find a partner and complete as much of the task below as you can. At the end of our time together, paste your HTML files into Crowdsource in order to receive credit.
Converting a Callback to a Promise
Imagine you wanted to write a REPL for a programming language. In Java, you’d write code like this:
Scanner in = new Scanner(System.in);
while (true) {
System.out.print("> ");
String line = in.nextLine();
// process line
}
This is harder to write in JavaScript. Many JavaScript libraries are designed around callbacks to handle asynchronous operations like getting user input. That’s sad. However, if you had promises, you could write code pretty close to the Java code above. Sometimes an alternative version of the library based on promises is available. But not always. The good news is that you can convert a callback scheme into a promise scheme. Consider this delayed console message:
setTimeout(() => {
console.log('Happy birthday!');
}, 1000);
To frame this synchronously, you’d write it by awaiting an asynchronous delay function:
await delay(1000);
console.log('Happy birthday!');
There is no delay
function. But you can write one:
function delay(millis) {
return new Promise(resolve => {
setTimeout(() -> resolve(), millis);
});
}
This function creates a promise. The promise constructor expects a function as its parameter. It hands that function another function named resolve
, which must be called when the asynchronous operation finishes. The callback that we provide our asynchronous function will call resolve
instead of logging a message.
That rounds out the conversion. There’s just one gotcha. Node.js doesn’t allow awaiting at the top-level. So we wrap the sequential code up in a function:
function delay(millis) {
return new Promise(resolve => {
setTimeout(() => resolve(), millis);
});
}
async function main() {
await delay(1000);
console.log('Happy birthday!');
}
main();
If our asynchronous operation had produced a result, then we pass it along through the call to resolve
, and it becomes the value produced by the await
command:
function somePromiseOp(...) {
return new Promise(resolve => {
someCallbackOp(..., result => resolve(result));
});
}
async function main() {
const result = await somePromiseOp(...);
// process result
}
main();
Task 1
When you open up a terminal on your computer, you are given a shell. The shell prompts the user for a line of text. The first word in the line is a shell command or the name of an executable file. Any other words are passed as command-line arguments to the command or executable. Your task in this lab is to write your own shell using Node.js.
Support the following commands, listed in the order of recommended implementation:
exit
stops the shell.pwd
prints the current working directory. This implies that you have some means of tracking the shell’s current working directory.ls
lists the files in the current working directory.cd DIRECTORY
sets the current directory. Its command-line argument may be either..
to move up one directory or the name of a child directory in the current directory.serve PORT FILE
makes the specified file accessible viahttp://localhost:PORT
. The file must be a child of the current working directory.
Include the following dependencies in your solution:
const fsPromises = require('fs').promises;
const path = require('path');
const http = require('http');
If you are using a lab computer, you’ll need to install NVM as described in All Over the Web. You’ll additionally need to consult the Node.js documentation.
Getting User Input
Use the builtin readline
to get user input. It is imported and configured to read from standard input and write to standard output with this statement:
const readline = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
Getting user input with readline
is awkward because of its orientation toward asynchonicity. Its question
method expects you provide a callback to handle the answer:
readline.question(prompt, answer => {
console.log(`You answered: ${answer}`);
});
However, you can translate this function into one that yields a promise. Then you can use async
and await
to write sequential code.
TODO
The next step of your learning is to complete the following tasks:
- Read the chapter Providing a Web Service in All Over the Web. Be sure to follow along to the code samples. Recreate each exercise yourself.
- Fix any issues identified by your peers before the end of Thursday.
- Research some aspect of web development on your own. Find articles and videos beyond what’s assigned. Summarize what you learn in a couple paragraphs of your own words and a snippet of source code in your next blog entry before Friday morning. Clearly put the date of the blog entry on your index page. If any of the requirements is not met, you will not receive full credit.
See you next time.