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

BREAKING: fs.read() & fs.write() should return objects #449

Merged
merged 2 commits into from Jul 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -128,7 +128,7 @@ Methods
- [writeJsonSync](docs/writeJson-sync.md)


**NOTE:** You can still use the native Node.js methods. They are promisified and copied over to `fs-extra`.
**NOTE:** You can still use the native Node.js methods. They are promisified and copied over to `fs-extra`. See [notes on `fs.read()` & `fs.write()`](docs/fs-read-write.md)

### What happened to `walk()` and `walkSync()`?

Expand Down
39 changes: 39 additions & 0 deletions docs/fs-read-write.md
@@ -0,0 +1,39 @@
# About `fs.read()` & `fs.write()`

[`fs.read()`](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) & [`fs.write()`](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback) are different from other `fs` methods in that their callbacks are called with 3 arguments instead of the usual 2 arguments.

If you're using them with callbacks, they will behave as usual. However, their promise usage is a little different. `fs-extra` promisifies these methods like [`util.promisify()`](https://nodejs.org/api/util.html#util_util_promisify_original) (only availible in Node 8+) does.

Here's the example promise usage:

## `fs.read()`

```js
// Basic promises
fs.read(fd, buffer, offset, length, position)
.then(results => {
console.log(results)
// { bytesRead: 20, buffer: <Buffer 0f 34 5d ...> }
})

// Async/await usage:
async function example () {
const { bytesRead, buffer } = await fs.read(fd, buffer, offset, length, position)
}
```

## `fs.write()`

```js
// Basic promises
fs.write(fd, buffer, offset, length, position)
.then(results => {
console.log(results)
// { bytesWritten: 20, buffer: <Buffer 0f 34 5d ...> }
})

// Async/await usage:
async function example () {
const { bytesWritten, buffer } = await fs.write(fd, buffer, offset, length, position)
}
```
154 changes: 154 additions & 0 deletions lib/fs/__tests__/multi-param.test.js
@@ -0,0 +1,154 @@
'use strict'
/* eslint-env mocha */
const assert = require('assert')
const path = require('path')
const crypto = require('crypto')
const os = require('os')
const semver = require('semver')
const fs = require('../..')

const SIZE = 1000

// Used for tests on Node 7.2.0+ only
const onNode7it = semver.gte(process.version, '7.2.0') ? it : it.skip

describe('fs.read()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD

beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'read-test-file')
TEST_DATA = crypto.randomBytes(SIZE)
fs.writeFileSync(TEST_FILE, TEST_DATA)
TEST_FD = fs.openSync(TEST_FILE, 'r')
})

afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})

describe('with promises', () => {
it('returns an object', () => {
return fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, 0)
.then(results => {
const bytesRead = results.bytesRead
const buffer = results.buffer
assert.equal(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})

it('returns an object when position is not set', () => {
return fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE)
.then(results => {
const bytesRead = results.bytesRead
const buffer = results.buffer
assert.equal(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})
})

describe('with callbacks', () => {
it('works', done => {
fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, 0, (err, bytesRead, buffer) => {
assert.ifError(err)
assert.equal(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})

it('works when position is null', done => {
fs.read(TEST_FD, Buffer.alloc(SIZE), 0, SIZE, null, (err, bytesRead, buffer) => {
assert.ifError(err)
assert.equal(bytesRead, SIZE, 'bytesRead is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})
})
})

describe('fs.write()', () => {
let TEST_FILE
let TEST_DATA
let TEST_FD

beforeEach(() => {
TEST_FILE = path.join(os.tmpdir(), 'fs-extra', 'write-test-file')
TEST_DATA = crypto.randomBytes(SIZE)
fs.ensureDirSync(path.dirname(TEST_FILE))
TEST_FD = fs.openSync(TEST_FILE, 'w')
})

afterEach(() => {
return fs.close(TEST_FD)
.then(() => fs.remove(TEST_FILE))
})

describe('with promises', () => {
it('returns an object', () => {
return fs.write(TEST_FD, TEST_DATA, 0, SIZE, 0)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.equal(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})

onNode7it('returns an object when minimal arguments are passed', () => {
return fs.write(TEST_FD, TEST_DATA)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.equal(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
})
})

it('returns an object when writing a string', () => {
const message = 'Hello World!'
return fs.write(TEST_FD, message)
.then(results => {
const bytesWritten = results.bytesWritten
const buffer = results.buffer
assert.equal(bytesWritten, message.length, 'bytesWritten is correct')
assert.equal(buffer, message, 'data is correct')
})
})
})

describe('with callbacks', () => {
it('works', done => {
fs.write(TEST_FD, TEST_DATA, 0, SIZE, 0, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.equal(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})

onNode7it('works when minimal arguments are passed', done => {
fs.write(TEST_FD, TEST_DATA, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.equal(bytesWritten, SIZE, 'bytesWritten is correct')
assert(buffer.equals(TEST_DATA), 'data is correct')
done()
})
})

it('works when writing a string', done => {
const message = 'Hello World!'
return fs.write(TEST_FD, message, (err, bytesWritten, buffer) => {
assert.ifError(err)
assert.equal(bytesWritten, message.length, 'bytesWritten is correct')
assert.equal(buffer, message, 'data is correct')
done()
})
})
})
})
44 changes: 42 additions & 2 deletions lib/fs/index.js
Expand Up @@ -21,7 +21,6 @@ const api = [
'lstat',
'mkdir',
'open',
'read',
'readFile',
'readdir',
'readlink',
Expand All @@ -33,7 +32,6 @@ const api = [
'truncate',
'unlink',
'utimes',
'write',
'writeFile'
]
// fs.mkdtemp() was added in Node.js v5.10.0, so check if it exists
Expand All @@ -59,3 +57,45 @@ exports.exists = function (filename, callback) {
return fs.exists(filename, resolve)
})
}

// fs.read() & fs.write need special treatment due to multiple callback args

exports.read = function (fd, buffer, offset, length, position, callback) {
if (typeof callback === 'function') {
return fs.read(fd, buffer, offset, length, position, callback)
}
return new Promise((resolve, reject) => {
fs.read(fd, buffer, offset, length, position, (err, bytesRead, buffer) => {
if (err) return reject(err)
resolve({ bytesRead, buffer })
})
})
}

// Function signature can be
// fs.write(fd, buffer[, offset[, length[, position]]], callback)
// OR
// fs.write(fd, string[, position[, encoding]], callback)
// so we need to handle both cases
exports.write = function (fd, buffer, a, b, c, callback) {
if (typeof arguments[arguments.length - 1] === 'function') {
return fs.write(fd, buffer, a, b, c, callback)
}

// Check for old, depricated fs.write(fd, string[, position[, encoding]], callback)
if (typeof buffer === 'string') {
return new Promise((resolve, reject) => {
fs.write(fd, buffer, a, b, (err, bytesWritten, buffer) => {
if (err) return reject(err)
resolve({ bytesWritten, buffer })
})
})
}

return new Promise((resolve, reject) => {
fs.write(fd, buffer, a, b, c, (err, bytesWritten, buffer) => {
if (err) return reject(err)
resolve({ bytesWritten, buffer })
})
})
}
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -48,6 +48,7 @@
"read-dir-files": "^0.1.1",
"rimraf": "^2.2.8",
"secure-random": "^1.1.1",
"semver": "^5.3.0",
"standard": "^10.0.2",
"standard-markdown": "^2.3.0"
},
Expand Down