Node Wait for File to Finish Reading
As a Node.js developer, at that place's a good hazard that at some point yous've imported the fs
module and written some code that's interacted with the file organization.
What you might not know is that the fs
module is a fully-featured, standards-based, cross-platform module that exposes not one, but 3 APIs that cater to synchronous and asynchronous programming styles.
In this article, we will thoroughly explore the world of Node.js file processing in Windows and Linux systems, with a focus on the fs
module's hope-based API.
A note before we brainstorm
All examples in this commodity are intended to be run in a Linux environment, just many will work in Windows, too. Await for notes throughout the commodity highlighting examples that won't work in Windows. Regarding macOS — in most cases, the fs
module works the aforementioned way it would on Linux, but in that location are some macOS-specific behaviors that are not covered in this article. Refer to the official Node.js documentation for macOS nuances.
The full source code for all examples is bachelor on my GitHub under briandesousa/node-file-procedure.
Introducing the fs
module
The fs
module is a cadre module built into Node.js. It has been effectually since the kickoff, all the way back to the original Node.js v0.ten releases.
Since its earliest days, the fs
module has been aligned with POSIX file organisation standards. This means the code you write is somewhat portable across multiple operating systems, though especially between different flavors of Unix and Linux.
Although Windows is not a POSIX-compliant operating system, about of the fs
module'south functions will still work. However, in that location are functions that are non portable simply because certain file arrangement capabilities do non exist or are implemented differently in Windows.
As we review the fs
module's functions, keep in mind that the post-obit functions will render errors or volition take unexpected results on Windows:
- Functions to alter file permissions and ownership:
-
chmod()
-
chown()
-
- Functions to piece of work with hard and soft links:
-
link()
-
symlink()
-
readlink()
-
lutimes()
-
lchmod()
-
lchown()
-
- Some metadata is either non fix or displays unexpected values when using
stat()
andlstat()
Since Node v10, the fs
module has included three unlike APIs: synchronous, callback, and promise. All 3 APIs expose the same ready of file organization operations.
This article will focus on the newer hope-based API. However, there may be circumstances where you desire or need to utilize the synchronous or callback APIs. For that reason, let'southward accept a moment to compare all three APIs.
Comparing the FS module APIs
Synchronous API
The synchronous API exposes a fix of functions that block execution to perform file arrangement operations. These functions tend to be the simplest to use when you're just getting started.
On the other hand, they are thread-blocking, which is very contrary to the non-blocking I/O design of Node.js. Even so, there are times where you lot must process a file synchronously.
Here is an example of using the synchronous API to read the contents of a file:
import * as fs from 'fs'; const data = fs.readFileSync(path); panel.log(information);
Callback API
The callback API allows you to collaborate with the file system in an asynchronous fashion. Each of the callback API functions accept a callback function that is invoked when the performance is completed. For example, we can call the readFile
function with an arrow role that receives an mistake if at that place is a failure or receives the data if the file is read successfully:
import * as fs from 'fs'; fs.readFile(path, (err, data) => { if (err) { console.error(err); } else { console.log(`file read complete, information: ${data}`); } });
This is a non-blocking approach that is usually more than suitable for Node.js applications, just information technology comes with its own challenges. Using callbacks in asynchronous programming often results in callback hell. If you're not careful with how you structure your code, y'all may end up with a complex stack of nested callback functions that can be difficult to read and maintain.
Promise API
If synchronous APIs should be avoided when possible, and callback APIs may non be ideal, that leaves usa with the promise API:
import * as fsPromises from 'fs/promises'; async office usingPromiseAPI(path) { const hope = fsPromises.readFile(path); panel.log('practice something else'); return expect promise; }
The first thing you might notice is the divergence in this import argument compared to the previous examples: the promise API is available from the promises
subpath. If you are importing all functions in the promise API, the convention is to import them as fsPromises
. Synchronous and callback API functions are typically imported as fs
.
If yous desire to proceed example code compact, import statements will be omitted from subsequent examples. Standard import naming conventions will be used to differentiate betwixt APIs: fs
to access synchronous and callback functions, and fsPromises
to access promise functions.
The promise API allows you to take advantage of JavaScript's async/await syntactic sugar to write asynchronous code in a synchronous way. The readFile()
function called on line four above returns a promise. The code that follows appears to be executed synchronously. Finally, the promise is returned from the function. The expect
operator is optional, but since we have included it, the function will wait for the file operation to consummate before returning.
It's fourth dimension to take the hope API for a test drive. Get comfortable. At that place are quite a few functions to cover, including ones that create, read, and update files and file metadata.
Working with files
Using file handles
The promise API provides ii different approaches to working with files.
The first approach uses a top-level ready of functions that accept file paths. These functions manage the lifecycle of file and directory resource handles internally. You don't need to worry about calling a close()
office when you are done with the file or directory.
The second approach uses a set of functions available on a FileHandle
object. A FileHandle
acts as a reference to a file or directory on the file system. Here is how y'all can obtain a FileHandle
object:
async function openFile(path) { permit fileHandle; try { fileHandle = await fsPromises.open up(path, 'r'); console.log(`opened ${path}, file descriptor is ${fileHandle.fd}`); const data = fileHandle.read() } grab (err) { panel.error(err.message); } finally { fileHandle?.shut(); } }
On line 4 to a higher place, we employ fsPromises.open up()
to create a FileHandle
for a file. We laissez passer the r
flag to indicate that the file should be opened in read-only mode. Any operations that try to modify the file volition neglect. (You tin can also specify other flags.)
The file'southward contents are read using the read()
office, which is directly bachelor from the file handle object. On line 10, we need to explicitly close the file handle to avert potential retentivity leaks.
All of the functions available in the FileHandle
class are too available as acme-level functions. Nosotros'll continue to explore meridian-level functions, but it is good to know that this approach is available as well.
Reading files
Reading a file seems like such a simple task. However, in that location are several dissimilar options that tin be specified depending on what you lot need to do with a file:
// example 1: simple read const data = await fsPromises.readFile(path); // example ii: read a file that doesn't exist (creates a new file) const noData = await fsPromises.readFile(path, { flag: 'w'}); // case iii: read a file and render its contents every bit a base64-encoded string const base64data = wait fsPromises.readFile(path, { encoding: 'base64' }); // instance 4: read a file simply abort the performance before it completes const controller = new AbortController(); const { bespeak } = controller; const promise = fsPromises.readFile(path, { betoken: signal }); panel.log(`started reading file at ${path}`); controller.abort(); console.log('read functioning aborted before information technology could be completed') await hope;
Case 1 is as simple as it gets, if all you desire to exercise is get the contents of a file.
In example two, we don't know if the file exists, so nosotros pass the w
file system flag to create it kickoff, if necessary.
Example iii demonstrates how you alter the format of the information returned.
Example 4 demonstrates how to interrupt a file read functioning and abort it. This could be useful when reading files that are big or tiresome to read.
Copying files
The copyFile
function tin can brand a copy of a file and requite you lot some control over what happens if the destination file exists already:
// example 1: create a copy, overwite the destination file if information technology exists already look fsPromises.copyFile('source.txt', 'dest.txt'); // instance 2: create a copy only fail because the destination file exists already await fsPromises.copyFile('source.txt', 'dest.txt', fs.constants.COPYFILE_EXCL); // Fault: EEXIST: file already exists, copyfile 'source.txt' -> 'dest.txt'
Instance one will overwrite dest.txt
if information technology exists already. In example 2, we laissez passer in the COPYFILE_EXCL
flag to override the default behavior and fail if dest.txt
exists already.
Writing files
There are three ways to write to a file:
- Suspend to a file
- Write to a file
- Truncate a file
Each of these functions helps to implement different utilize cases.
// example i: append to an existing file // content of information.txt earlier: 12345 await fsPromises.appendFile('information.txt', '67890'); // content of data.txt afterwards: 1234567890 // example ii: append to a file that doesn't exist yet await fsPromises.appendFile('data2.txt', '123'); // Error: ENOENT: no such file or directory, open 'data2.txt' // example 3: write to an existing file // content of data3.txt before: 12345 await fsPromises.writeFile('data3.txt', '67890'); // content of data3.txt after: 67890 // instance 4: write to a file that doesn't be yet (new file is created) await fsPromises.writeFile('data4.txt', '12345'); // example 5: truncate information in an existing file // content of data5.txt before: 1234567890 await fsPromises.truncate('data5.txt', 5); // content of data5.txt later on: 12345
Examples ane and 2 demonstrate how to use the appendFile
function to append data to existing or new files. If a file doesn't exist, appendFile
will create information technology first.
Examples 3 and iv demonstrate how to use the writeFile
function to write to existing or new files. The writeFile
part will also create a file if it doesn't be earlier writing to it. Yet, if the file already exists and contains data, the contents of the file is overwritten without alert.
Case 5 demonstrates how to apply the truncate
function to trim the contents of a file. The arguments that are passed to this role can be confusing at first. You might expect a truncate
part to have the number of characters to strip from the cease of the file, simply actually we need to specify the number of characters to retain. In the example above, yous can see that we entered a value of 5
to the truncate
office, which removed the terminal 5 characters from the string 1234567890
.
Watching files
The promise API provides a single, performant watch
part that can lookout man a file for changes.
const abortController = new AbortController(); const { signal } = abortController; setTimeout(() => abortController.arrest(), 3000); const watchEventAsyncIterator = fsPromises.sentry(path, { betoken }); setTimeout(() => { fs.writeFileSync(path, 'new information'); panel.log(`modified ${path}`); }, 1000); for look (const event of watchEventAsyncIterator) { console.log(`'${event.eventType}' watch result was raised for ${outcome.filename}`); } // console output: // modified ./data/watchTest.txt // 'alter' watch event was raised for watchTest.txt // sentinel on ./data/watchTest.txt aborted
The sentinel
office can watch a file for changes indefinitely. Each fourth dimension a alter is observed, a lookout man event is raised. The sentinel
role returns an async iterable, which is essentially a way for the function to return an unbounded serial of promises. On line 12, we have reward of the for await … of
syntactic sugar to await for and iterate each watch effect as it is received.
In that location is a good chance y'all don't want to endlessly watch a file for changes. The watch tin can be aborted by using a special signal object that can be triggered as required. On lines ane to 2, we create an case of AbortController
, which gives us access to an instance of AbortSignal
that is ultimately passed to the watch
function. In this case, we telephone call the signal object's abort()
role afterwards a fixed period of time (specified on line 3), simply you tin abort nevertheless and whenever you need to.
The lookout man
part can as well be used to sentinel the contents of a directory. It accepts an optional recursive
option that determines whether all subdirectories and files are watched.
File metadata
So far, we have focused on reading and modifying the contents of a file, but yous may also need to read and update a file'south metadata. File metadata includes its size, type, permissions, and other file system backdrop.
The stat
role is used to retrieve file metadata, or "statistics" like file size, permissions, and ownership.
// go all file metadata const fileStats = await fsPromises.stat('file1.txt'); panel.log(fileStats) // console output: // Stats { // dev: 2080, // style: 33188, // nlink: 1, // uid: chiliad, // gid: thou, // rdev: 0, // blksize: 4096, // ino: 46735, // size: 29, // blocks: 8, // atimeMs: 1630038059841.8247, // mtimeMs: 1630038059841.8247, // ctimeMs: 1630038059841.8247, // birthtimeMs: 1630038059801.8247, // atime: 2021-08-27T04:20:59.842Z, // mtime: 2021-08-27T04:xx:59.842Z, // ctime: 2021-08-27T04:20:59.842Z, // birthtime: 2021-08-27T04:20:59.802Z // } console.log(`size of file1.txt is ${fileStats.size}`);
This case demonstrates the full list of metadata that tin can exist retrieved for a file or directory.
Continue in listen that some of this metadata is Os-dependent. For example, the uid
and gid
backdrop correspond the user and group owners — a concept that is applicative to Linux and macOS file systems, merely not Windows file systems. Zeroes are returned for these 2 backdrop when running in this function on Windows.
Some file metadata can be manipulated. For case, the utimes
office is used to update the access and modification timestamps on a file:
const newAccessTime = new Appointment(2020,0,i); const newModificationTime = new Engagement(2020,0,1); wait fsPromises.utimes('test1.txt', newAccessTime, newModificationTime);
The realpath
office is useful for resolving relative paths and symbolic links to full paths:
// convert a relative path to a full path const realPath = wait fsPromises.realpath('./test1.txt'); console.log(realPath); // console output: /home/brian/test1.txt // resolve the real path of a symbolic link pointing to /dwelling house/brian/test1.txt const symLinkRealPath = await fsPromises.realpath('./symlink1'); panel.log(symLinkRealPath); // console output: /habitation/brian/test1.txt
File permissions and ownership
Please go along in listen every bit we go on in this section that file permission and buying functions are applicable to Unix, Linux and macOS operating systems. These functions yield unexpected results on Windows.
If you are non sure whether your awarding has the necessary permissions to access or execute files on the file organization, you tin utilize the access
function to test it:
// case 1: check if a file can be accessed effort { wait fsPromises.admission('test1.txt'); console.log('test1.txt can be accessed'); } grab (err) { // EACCES: permission denied, access 'test1.txt' } // case 2: cheque if a file tin can be executed (applies to Unix/Linux-based systems) try { look fsPromises.access('test2.txt', fs.constants.X_OK); } take hold of(err) { // EACCES: permission denied, access 'test2.txt' }
File permissions can be modified using the chmod
function. For instance, we can remove execute access from a file by passing a special mode string:
// remove all execute access from a file wait fsPromises.chmod('test1.txt', '00666');
The 00666
style cord is a special five-digit number that is equanimous of multiple bit masks that draw file attributes including permissions. The last three digits are equivalent to the three-digit permission style you might exist used to passing to chmod
on Linux. The fs
module documentation provides a list of bit masks that can be used translate this fashion string.
File ownership tin besides be modified using the chown
function:
// set up user and grouping ownership on a file const root_uid= 0; const root_gid = 0; expect fsPromises.chown('test1.txt', root_uid, root_gid);
In this case, we update the file so that it is owned by the root user and root group. The uid
of the root user and gid
of the root grouping are always 0
on Linux.
Working with links
Tip: Link functions are applicable to Unix/Linux operating systems. These functions yield unexpected results on Windows.
The fs
module provides a variety of functions you lot tin use to work with hard and soft, or symbolic, links. Many of the file functions we've already seen have equivalent versions for working with links. In most cases, they operate identically, too.
Before we start creating links, let'southward have a quick refresher on the two types of links we'll be working with.
Hard vs. soft links
Hard and soft links are special types of files that point to other files on the file system. A soft link becomes invalid if the file it is linked to is deleted.
On the other paw, a hard link pointing to a file will however exist valid and contain the file'southward contents fifty-fifty if the original file is deleted. Hard links don't point to a file, merely rather a file'south underlying data. This data is referred to as the inode on Unix/Linux file systems.
We tin easily create soft and hard links with the fs
module. Employ the symlink
office to create soft links and the link
function to create hard links.
// create a soft link const softLink = await fsPromises.symlink('file.txt', 'softLinkedFile.txt'); // create a difficult link const hardLink = await fsPromises.link('file.txt', 'hardLinkedFile.txt');
What if you want to determine the underlying file that a link points to? This is where the readlink
role comes in.
>// read a soft link panel.log(await fsPromises.readlink('softLinkedFile.txt')); // output: file.txt // read a difficult link... and fail console.log(look fsPromises.readLink('hardLinkedFile.txt')); // output: EINVAL: invalid argument, readlink 'hardLinkedFile.txt'
The readlink
function can read soft links, but not hard links. A hard link is duplicate from the original file it links to. In fact, all files are technically hard links. The readlink
function essentially sees it as just another regular file and throws an EINVAL
fault.
The unlink
function tin can remove both hard and soft links:
// delete a soft link await fsPromises.unlink('softLinkedFile.txt'); // delete a hard link / file await fsPromises.unlink('hardLinkedFile.txt');
The unlink
function really serves as a general purpose function that can also be used to delete regular files, since they are essentially the same as a difficult link. Aside from the link
and unlink
functions, all other link functions are intended to be used with soft links.
You tin modify a soft link's metadata much similar y'all would a normal file's:
// view soft link meta information const linkStats = await fsPromises.lstat(path); // update access and modify timestamps on a soft link const newAccessTime = new Date(2020,0,1); const newModifyTime = new Date(2020,0,1); await fsPromises.lutimes('softLinkedFile.txt', newAccessTime, newModifyTime); // remove all execute access from a soft link expect fsPromises.lchmod('softLinkedFile.txt', '00666'); // set user and group buying on a soft link const root_uid= 0; const root_gid = 0; expect fsPromises.lchown('softLinkedFile.txt', root_uid, root_gid);
Bated from each part being prefixed with an l
, these functions operate identically to their equivalent file functions.
Working with directories
We can't but terminate at file processing. If you are working with files, it is inevitable that yous will need to piece of work with directories likewise. The fs
module provides a diversity of functions for creating, modifying, and deleting directories.
Much like the open up
function we saw earlier, the opendir
function returns a handle to a directory in the form of a Dir
object. The Dir
object exposes several functions that can exist used to operate on that directory:
allow dir; try { dir = await fsPromises.opendir('sampleDir'); dirents = await dir.read(); } take hold of (err) { console.log(err); } finally { dir.close(); }
Be sure to call the close
part to release the handle on the directory when you are done with it.
The fs
module also includes functions that hide the opening and closing of directory resources handles for you. For instance, you can create, rename, and delete directories:
// example i: create a directory await fsPromises.mkdir('sampleDir'); // example ii: create multiple nested directories await fsPromises.mkdir('nested1/nested2/nested3', { recursive: true }); // example 3: rename a directory await fsPromises.rename('sampleDir', 'sampleDirRenamed'); // example 4: remove a directory await fsPromises.rmdir('sampleDirRenamed'); // example 5: remove a directory tree await fsPromises.rm('nested1', { recursive: true }); // example 6: remove a directory tree, ignore errors if it doesn't exist await fsPromises.rm('nested1', { recursive: true, forcefulness: true });
Examples two, 5, and 6 demonstrate the recursive
option, which is especially helpful if you lot don't know if a path volition exist before creating or deleting it.
At that place are two options to read the contents of a directory. By default, the readdir
function returns a list of the names of all the files and folders direct below the requested directory.
Yous can laissez passer the withFileTypes
choice to get a list of Dirent
directory entry objects instead. These objects contain the proper noun and type of each file arrangement object in the requested directory. For example:
// example one: get names of files and directories const files = wait fsPromises.readdir('anotherDir'); for (const file in files) { console.log(file); } // example 2: become files and directories as 'Dirent' directory entry objects const dirents = await fsPromises.readdir('anotherDir', {withFileTypes: true}); for (const entry in dirents) { if (entry.isFile()) { panel.log(`file name: ${entry.name}`); } else if (entry.isDirectory()) { panel.log(`directory name: ${entry.name}`); } else if (entry.isSymbolicLink()) { console.log(`symbolic link name: ${entry.proper name}`); } }
The readdir
role does not provide a recursive option to read the contents of sub-directories. You'll have to write your ain recursive function or rely on a third-party module like recursive-readdir]()
.
Close()
It'southward time to close()
the resource handle for this article. We have taken a thorough expect at how to work with files, links, and directories using the Node.js fs
module. File processing is available in Node.js out of the box, fully featured and prepare to apply.
200's only Monitor failed and wearisome network requests in production
Deploying a Node-based web app or website is the easy part. Making sure your Node example continues to serve resources to your app is where things get tougher. If y'all're interested in ensuring requests to the backend or 3rd party services are successful, try LogRocket. https://logrocket.com/signup/
LogRocket is similar a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, y'all can aggregate and report on problematic network requests to quickly understand the root cause.
LogRocket instruments your app to record baseline operation timings such as folio load time, time to first byte, tedious network requests, and also logs Redux, NgRx, and Vuex actions/country. First monitoring for gratis.
Source: https://blog.logrocket.com/file-processing-node-js-comprehensive-guide/
0 Response to "Node Wait for File to Finish Reading"
Post a Comment