From 6a4c236a49f21e9e3d218a24965996a2734ffaa6 Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Fri, 10 Apr 2026 00:30:39 +0800 Subject: [PATCH 1/3] fs: always return promise from openAsBlob() Wrap path validation and blob creation in try/catch. Any synchronous errors become Promise rejections. Ensures the function always returns a Promise. Fixes: https://github.com/nodejs/node/issues/62418 --- lib/fs.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index a7b56ee86171e3..778eed95db6c7d 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -574,8 +574,12 @@ function openAsBlob(path, options = kEmptyObject) { // The underlying implementation here returns the Blob synchronously for now. // To give ourselves flexibility to maybe return the Blob asynchronously, // this API returns a Promise. - path = getValidatedPath(path); - return PromiseResolve(createBlobFromFilePath(path, { type })); + try { + path = getValidatedPath(path); + return PromiseResolve(createBlobFromFilePath(path, { type })); + } catch (error) { + return Promise.reject(error); + } } /** From d42280de03acb93c2abe7796885fd4a7310f555a Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Mon, 20 Apr 2026 21:58:53 +0800 Subject: [PATCH 2/3] fs: always return promise form openAsBlob() Wrap entire function in try/catch. Ensure that the function always returns promise. Replace Promise.reject with primordial. --- lib/fs.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 778eed95db6c7d..26ac282a1167d6 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -36,6 +36,7 @@ const { ObjectDefineProperty, Promise, PromisePrototypeThen, + PromiseReject, PromiseResolve, ReflectApply, SafeMap, @@ -568,17 +569,17 @@ function openSync(path, flags, mode) { * @returns {Promise} */ function openAsBlob(path, options = kEmptyObject) { - validateObject(options, 'options'); - const type = options.type || ''; - validateString(type, 'options.type'); - // The underlying implementation here returns the Blob synchronously for now. - // To give ourselves flexibility to maybe return the Blob asynchronously, - // this API returns a Promise. try { + validateObject(options, 'options'); + const type = options.type || ''; + validateString(type, 'options.type'); + // The underlying implementation here returns the Blob synchronously for now. + // To give ourselves flexibility to maybe return the Blob asynchronously, + // this API returns a Promise. path = getValidatedPath(path); return PromiseResolve(createBlobFromFilePath(path, { type })); } catch (error) { - return Promise.reject(error); + return PromiseReject(error); } } From 08c326a13ff76fd4a8899bf8526e42eedcb4f62d Mon Sep 17 00:00:00 2001 From: Matt Skelley Date: Tue, 21 Apr 2026 17:34:11 +0800 Subject: [PATCH 3/3] fs: return promise when error throws Revert original behaviour. Move validation outside try/catch. Add tests. Ref: https://github.com/nodejs/node/pull/62655#discussion_r3112238114 --- lib/fs.js | 12 +++++------ .../test-fs-openasblob-promise-contract.js | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-fs-openasblob-promise-contract.js diff --git a/lib/fs.js b/lib/fs.js index 26ac282a1167d6..a32e8a89e3ff09 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -569,13 +569,13 @@ function openSync(path, flags, mode) { * @returns {Promise} */ function openAsBlob(path, options = kEmptyObject) { + validateObject(options, 'options'); + const type = options.type || ''; + validateString(type, 'options.type'); + // The underlying implementation here returns the Blob synchronously for now. + // To give ourselves flexibility to maybe return the Blob asynchronously, + // this API returns a Promise. try { - validateObject(options, 'options'); - const type = options.type || ''; - validateString(type, 'options.type'); - // The underlying implementation here returns the Blob synchronously for now. - // To give ourselves flexibility to maybe return the Blob asynchronously, - // this API returns a Promise. path = getValidatedPath(path); return PromiseResolve(createBlobFromFilePath(path, { type })); } catch (error) { diff --git a/test/parallel/test-fs-openasblob-promise-contract.js b/test/parallel/test-fs-openasblob-promise-contract.js new file mode 100644 index 00000000000000..0803725c888f63 --- /dev/null +++ b/test/parallel/test-fs-openasblob-promise-contract.js @@ -0,0 +1,21 @@ +'use strict'; + +const assert = require('assert'); +const fs = require('fs'); + +// Verify invalid paths throw synchronously and return a Promise +(async () => { + let promise; + assert.doesNotThrow(() => { promise = fs.openAsBlob('does-not-exist'); }); + assert.strictEqual(typeof promise?.then, 'function'); + await assert.rejects(promise, { code: 'ERR_INVALID_ARG_VALUE' }); +})(); + + +// Verify that invalid arguments throw synchronously and return a Promise +(async () => { + let promise; + assert.doesNotThrow(() => { promise = fs.openAsBlob(123); }); + assert.strictEqual(typeof promise?.then, 'function'); + await assert.rejects(promise, { code: 'ERR_INVALID_ARG_TYPE' }); +})(); \ No newline at end of file