mirror of
https://github.com/denoland/deno.git
synced 2025-09-28 13:14:48 +00:00
fix: Node polyfill fsAppend rework (#4322)
* My original implementation of `fs.appendFile` used an async API, which, though it would work fine as a polyfill, wasn't an exact match with the Node API. This PR reworks that API to mimic the Node API fully as a synchronous void function with an async internal implementation. * Refactor move of other internal fs `dirent` and `dir` classes to the _fs internal directory.
This commit is contained in:
parent
3ed6ccc905
commit
cabe63eb05
7 changed files with 111 additions and 87 deletions
|
@ -1,18 +1,18 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
import { FileOptions, isFileOptions } from "./_fs_common.ts";
|
import { FileOptions, isFileOptions, CallbackWithError } from "./_fs_common.ts";
|
||||||
import { notImplemented } from "../_utils.ts";
|
import { notImplemented } from "../_utils.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: Also accept 'data' parameter as a Node polyfill Buffer type once this
|
* TODO: Also accept 'data' parameter as a Node polyfill Buffer type once this
|
||||||
* is implemented. See https://github.com/denoland/deno/issues/3403
|
* is implemented. See https://github.com/denoland/deno/issues/3403
|
||||||
*/
|
*/
|
||||||
export async function appendFile(
|
export function appendFile(
|
||||||
pathOrRid: string | number,
|
pathOrRid: string | number,
|
||||||
data: string,
|
data: string,
|
||||||
optionsOrCallback: string | FileOptions | Function,
|
optionsOrCallback: string | FileOptions | CallbackWithError,
|
||||||
callback?: Function
|
callback?: CallbackWithError
|
||||||
): Promise<void> {
|
): void {
|
||||||
const callbackFn: Function | undefined =
|
const callbackFn: CallbackWithError | undefined =
|
||||||
optionsOrCallback instanceof Function ? optionsOrCallback : callback;
|
optionsOrCallback instanceof Function ? optionsOrCallback : callback;
|
||||||
const options: string | FileOptions | undefined =
|
const options: string | FileOptions | undefined =
|
||||||
optionsOrCallback instanceof Function ? undefined : optionsOrCallback;
|
optionsOrCallback instanceof Function ? undefined : optionsOrCallback;
|
||||||
|
@ -23,6 +23,7 @@ export async function appendFile(
|
||||||
validateEncoding(options);
|
validateEncoding(options);
|
||||||
|
|
||||||
let rid = -1;
|
let rid = -1;
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
if (typeof pathOrRid === "number") {
|
if (typeof pathOrRid === "number") {
|
||||||
rid = pathOrRid;
|
rid = pathOrRid;
|
||||||
|
@ -38,7 +39,6 @@ export async function appendFile(
|
||||||
//TODO rework once https://github.com/denoland/deno/issues/4017 completes
|
//TODO rework once https://github.com/denoland/deno/issues/4017 completes
|
||||||
notImplemented("Deno does not yet support setting mode on create");
|
notImplemented("Deno does not yet support setting mode on create");
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = await Deno.open(pathOrRid, getOpenOptions(flag));
|
const file = await Deno.open(pathOrRid, getOpenOptions(flag));
|
||||||
rid = file.rid;
|
rid = file.rid;
|
||||||
}
|
}
|
||||||
|
@ -46,15 +46,26 @@ export async function appendFile(
|
||||||
const buffer: Uint8Array = new TextEncoder().encode(data);
|
const buffer: Uint8Array = new TextEncoder().encode(data);
|
||||||
|
|
||||||
await Deno.write(rid, buffer);
|
await Deno.write(rid, buffer);
|
||||||
callbackFn();
|
resolve();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
closeRidIfNecessary(typeof pathOrRid === "string", rid);
|
||||||
|
callbackFn();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
closeRidIfNecessary(typeof pathOrRid === "string", rid);
|
||||||
callbackFn(err);
|
callbackFn(err);
|
||||||
} finally {
|
});
|
||||||
if (typeof pathOrRid === "string" && rid != -1) {
|
}
|
||||||
|
|
||||||
|
function closeRidIfNecessary(isPathString: boolean, rid: number): void {
|
||||||
|
if (isPathString && rid != -1) {
|
||||||
//Only close if a path was supplied and a rid allocated
|
//Only close if a path was supplied and a rid allocated
|
||||||
Deno.close(rid);
|
Deno.close(rid);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,10 +105,7 @@ export function appendFileSync(
|
||||||
|
|
||||||
Deno.writeSync(rid, buffer);
|
Deno.writeSync(rid, buffer);
|
||||||
} finally {
|
} finally {
|
||||||
if (typeof pathOrRid === "string" && rid != -1) {
|
closeRidIfNecessary(typeof pathOrRid === "string", rid);
|
||||||
//Only close if a 'string' path was supplied and a rid allocated
|
|
||||||
Deno.close(rid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
import {
|
import { assertEquals, assertThrows, fail } from "../../testing/asserts.ts";
|
||||||
assertEquals,
|
|
||||||
assert,
|
|
||||||
assertThrows,
|
|
||||||
assertThrowsAsync
|
|
||||||
} from "../../testing/asserts.ts";
|
|
||||||
import { appendFile, appendFileSync } from "./_fs_appendFile.ts";
|
import { appendFile, appendFileSync } from "./_fs_appendFile.ts";
|
||||||
|
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
|
|
||||||
test({
|
test({
|
||||||
name: "No callback Fn results in Error",
|
name: "No callback Fn results in Error",
|
||||||
async fn() {
|
fn() {
|
||||||
await assertThrowsAsync(
|
assertThrows(
|
||||||
async () => {
|
() => {
|
||||||
await appendFile("some/path", "some data", "utf8");
|
appendFile("some/path", "some data", "utf8");
|
||||||
},
|
},
|
||||||
Error,
|
Error,
|
||||||
"No callback function supplied"
|
"No callback function supplied"
|
||||||
|
@ -25,22 +20,17 @@ test({
|
||||||
|
|
||||||
test({
|
test({
|
||||||
name: "Unsupported encoding results in error()",
|
name: "Unsupported encoding results in error()",
|
||||||
async fn() {
|
fn() {
|
||||||
await assertThrowsAsync(
|
assertThrows(
|
||||||
async () => {
|
() => {
|
||||||
await appendFile(
|
appendFile("some/path", "some data", "made-up-encoding", () => {});
|
||||||
"some/path",
|
|
||||||
"some data",
|
|
||||||
"made-up-encoding",
|
|
||||||
() => {}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
Error,
|
Error,
|
||||||
"Only 'utf8' encoding is currently supported"
|
"Only 'utf8' encoding is currently supported"
|
||||||
);
|
);
|
||||||
await assertThrowsAsync(
|
assertThrows(
|
||||||
async () => {
|
() => {
|
||||||
await appendFile(
|
appendFile(
|
||||||
"some/path",
|
"some/path",
|
||||||
"some data",
|
"some data",
|
||||||
{ encoding: "made-up-encoding" },
|
{ encoding: "made-up-encoding" },
|
||||||
|
@ -75,31 +65,47 @@ test({
|
||||||
write: true,
|
write: true,
|
||||||
read: true
|
read: true
|
||||||
});
|
});
|
||||||
let calledBack = false;
|
await new Promise((resolve, reject) => {
|
||||||
await appendFile(file.rid, "hello world", () => {
|
appendFile(file.rid, "hello world", err => {
|
||||||
calledBack = true;
|
if (err) reject();
|
||||||
|
else resolve();
|
||||||
});
|
});
|
||||||
assert(calledBack);
|
})
|
||||||
Deno.close(file.rid);
|
.then(async () => {
|
||||||
const data = await Deno.readFile(tempFile);
|
const data = await Deno.readFile(tempFile);
|
||||||
assertEquals(decoder.decode(data), "hello world");
|
assertEquals(decoder.decode(data), "hello world");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
fail("No error expected");
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
|
Deno.close(file.rid);
|
||||||
await Deno.remove(tempFile);
|
await Deno.remove(tempFile);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test({
|
test({
|
||||||
name: "Async: Data is written to passed in file path",
|
name: "Async: Data is written to passed in file path",
|
||||||
async fn() {
|
async fn() {
|
||||||
let calledBack = false;
|
|
||||||
const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
|
const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
|
||||||
await appendFile("_fs_appendFile_test_file.txt", "hello world", () => {
|
await new Promise((resolve, reject) => {
|
||||||
calledBack = true;
|
appendFile("_fs_appendFile_test_file.txt", "hello world", err => {
|
||||||
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
});
|
});
|
||||||
assert(calledBack);
|
})
|
||||||
|
.then(async () => {
|
||||||
assertEquals(Deno.resources(), openResourcesBeforeAppend);
|
assertEquals(Deno.resources(), openResourcesBeforeAppend);
|
||||||
const data = await Deno.readFile("_fs_appendFile_test_file.txt");
|
const data = await Deno.readFile("_fs_appendFile_test_file.txt");
|
||||||
assertEquals(decoder.decode(data), "hello world");
|
assertEquals(decoder.decode(data), "hello world");
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
fail("No error was expected: " + err);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
await Deno.remove("_fs_appendFile_test_file.txt");
|
await Deno.remove("_fs_appendFile_test_file.txt");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -107,16 +113,23 @@ test({
|
||||||
name:
|
name:
|
||||||
"Async: Callback is made with error if attempting to append data to an existing file with 'ax' flag",
|
"Async: Callback is made with error if attempting to append data to an existing file with 'ax' flag",
|
||||||
async fn() {
|
async fn() {
|
||||||
let calledBack = false;
|
|
||||||
const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
|
const openResourcesBeforeAppend: Deno.ResourceMap = Deno.resources();
|
||||||
const tempFile: string = await Deno.makeTempFile();
|
const tempFile: string = await Deno.makeTempFile();
|
||||||
await appendFile(tempFile, "hello world", { flag: "ax" }, (err: Error) => {
|
await new Promise((resolve, reject) => {
|
||||||
calledBack = true;
|
appendFile(tempFile, "hello world", { flag: "ax" }, err => {
|
||||||
assert(err);
|
if (err) reject(err);
|
||||||
|
else resolve();
|
||||||
});
|
});
|
||||||
assert(calledBack);
|
})
|
||||||
|
.then(() => {
|
||||||
|
fail("Expected error to be thrown");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
assertEquals(Deno.resources(), openResourcesBeforeAppend);
|
assertEquals(Deno.resources(), openResourcesBeforeAppend);
|
||||||
|
})
|
||||||
|
.finally(async () => {
|
||||||
await Deno.remove(tempFile);
|
await Deno.remove(tempFile);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
export type CallbackWithError = (err?: Error) => void;
|
||||||
|
|
||||||
export interface FileOptions {
|
export interface FileOptions {
|
||||||
encoding?: string;
|
encoding?: string;
|
||||||
mode?: number;
|
mode?: number;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
import { assert, assertEquals, fail } from "../testing/asserts.ts";
|
import { assert, assertEquals, fail } from "../../testing/asserts.ts";
|
||||||
import Dir from "./_fs_dir.ts";
|
import Dir from "./_fs_dir.ts";
|
||||||
import Dirent from "./_fs_dirent.ts";
|
import Dirent from "./_fs_dirent.ts";
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { notImplemented } from "./_utils.ts";
|
import { notImplemented } from "../_utils.ts";
|
||||||
|
|
||||||
export default class Dirent {
|
export default class Dirent {
|
||||||
constructor(private entry: Deno.FileInfo) {}
|
constructor(private entry: Deno.FileInfo) {}
|
|
@ -1,5 +1,5 @@
|
||||||
const { test } = Deno;
|
const { test } = Deno;
|
||||||
import { assert, assertEquals, assertThrows } from "../testing/asserts.ts";
|
import { assert, assertEquals, assertThrows } from "../../testing/asserts.ts";
|
||||||
import Dirent from "./_fs_dirent.ts";
|
import Dirent from "./_fs_dirent.ts";
|
||||||
|
|
||||||
class FileInfoMock implements Deno.FileInfo {
|
class FileInfoMock implements Deno.FileInfo {
|
Loading…
Add table
Add a link
Reference in a new issue