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

Promise #940

Merged
merged 5 commits into from
Jun 17, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# Changelog

### 3.4.0

* feature: ([#940](https://github.com/node-formidable/formidable/pull/940)) form.parse returns a promise if no callback is provided
* it resolves with and array `[fields, files]`


### 3.3.2

* feature: ([#855](https://github.com/node-formidable/formidable/pull/855))add options.createDirsFromUploads, see README for usage
* feature: ([#855](https://github.com/node-formidable/formidable/pull/855)) add options.createDirsFromUploads, see README for usage
* form.parse is an async function (ignore the promise)
* benchmarks: add e2e becnhmark with as many request as possible per second
* npm run to display all the commands
Expand Down
27 changes: 15 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,26 @@ Parse an incoming file upload, with the
import http from 'node:http';
import formidable, {errors as formidableErrors} from 'formidable';

const server = http.createServer((req, res) => {
const server = http.createServer(async (req, res) => {
if (req.url === '/api/upload' && req.method.toLowerCase() === 'post') {
// parse a file upload
const form = formidable({});

form.parse(req, (err, fields, files) => {
if (err) {
let fields;
let files;
try {
[fields, files] = await form.parse(req);
} catch (err) {
// example to check for a very specific error
if (err.code === formidableErrors.maxFieldsExceeded) {

}
console.error(err);
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
res.end(String(err));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
});

}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
return;
}

Expand Down Expand Up @@ -382,10 +383,9 @@ const options = {
```


### .parse(request, callback)
### .parse(request, ?callback)

Parses an incoming Node.js `request` containing form data. If `callback` is
provided, all fields and files are collected and passed to the callback.
Parses an incoming Node.js `request` containing form data. If `callback` is not provided a promise is returned.

```js
const form = formidable({ uploadDir: __dirname });
Expand All @@ -394,6 +394,9 @@ form.parse(req, (err, fields, files) => {
console.log('fields:', fields);
console.log('files:', files);
});

// with Promise
const [fields, files] = await form.parse(req);
```

You may overwrite this method if you are interested in directly accessing the
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "formidable",
"version": "3.3.2",
"version": "3.4.0",
"license": "MIT",
"description": "A node.js module for parsing form data, especially file uploads.",
"homepage": "https://github.com/node-formidable/formidable",
Expand Down
71 changes: 44 additions & 27 deletions src/Formidable.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,40 +178,55 @@ class IncomingForm extends EventEmitter {
return true;
}

// returns a promise if no callback is provided
async parse(req, cb) {
this.req = req;
let promise;

// Setup callback first, so we don't miss anything from data events emitted immediately.
if (cb) {
const callback = once(dezalgo(cb));
this.fields = {};
const files = {};

this.on('field', (name, value) => {
if (this.type === 'multipart' || this.type === 'urlencoded') {
if (!hasOwnProp(this.fields, name)) {
this.fields[name] = [value];
} else {
this.fields[name].push(value);
}
} else {
this.fields[name] = value;
}
if (!cb) {
let resolveRef;
let rejectRef;
promise = new Promise((resolve, reject) => {
resolveRef = resolve;
rejectRef = reject;
});
this.on('file', (name, file) => {
if (!hasOwnProp(files, name)) {
files[name] = [file];
cb = (err, fields, files) => {
if (err) {
rejectRef(err);
} else {
files[name].push(file);
resolveRef([fields, files]);
}
});
this.on('error', (err) => {
callback(err, this.fields, files);
});
this.on('end', () => {
callback(null, this.fields, files);
});
}
}
const callback = once(dezalgo(cb));
this.fields = {};
const files = {};

this.on('field', (name, value) => {
if (this.type === 'multipart' || this.type === 'urlencoded') {
if (!hasOwnProp(this.fields, name)) {
GrosSacASac marked this conversation as resolved.
Show resolved Hide resolved
this.fields[name] = [value];
} else {
this.fields[name].push(value);
}
} else {
this.fields[name] = value;
}
});
this.on('file', (name, file) => {
if (!hasOwnProp(files, name)) {
files[name] = [file];
} else {
files[name].push(file);
}
});
this.on('error', (err) => {
callback(err, this.fields, files);
});
this.on('end', () => {
callback(null, this.fields, files);
});

// Parse headers and setup the parser, ready to start listening for data.
await this.writeHeaders(req.headers);
Expand Down Expand Up @@ -240,7 +255,9 @@ class IncomingForm extends EventEmitter {
this._parser.end();
}
});

if (promise) {
return promise;
}
return this;
}

Expand Down
115 changes: 115 additions & 0 deletions test-node/standalone/promise.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {strictEqual, ok} from 'node:assert';
import { createServer, request } from 'node:http';
import formidable, {errors} from '../../src/index.js';
import test from 'node:test';

const PORT = 13539;

const isPromise = (x) => {
return x && typeof x === `object` && typeof x.then === `function`;
};

test('parse returns promise if no callback is provided', (t,done) => {
const server = createServer((req, res) => {
const form = formidable();

const promise = form.parse(req);
strictEqual(isPromise(promise), true);
promise.then(([fields, files]) => {
ok(typeof fields === 'object');
ok(typeof files === 'object');
res.writeHead(200);
res.end("ok")
}).catch(e => {
done(e)
})
});

server.listen(PORT, () => {
const chosenPort = server.address().port;
const body = `----13068458571765726332503797717\r
Content-Disposition: form-data; name="title"\r
\r
a\r
----13068458571765726332503797717\r
Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r
Content-Type: application/x-javascript\r
\r
\r
\r
a\r
b\r
c\r
d\r
\r
----13068458571765726332503797717--\r
`;
fetch(String(new URL(`http:localhost:${chosenPort}/`)), {
method: 'POST',

headers: {
'Content-Length': body.length,
Host: `localhost:${chosenPort}`,
'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717',
},
body
}).then(res => {
strictEqual(res.status, 200);
server.close();
done();
});

});
});

test('parse rejects with promise if it fails', (t,done) => {
const server = createServer((req, res) => {
const form = formidable({minFileSize: 10 ** 6}); // create condition to fail

const promise = form.parse(req);
strictEqual(isPromise(promise), true);
promise.then(() => {
done('should have failed')
}).catch(e => {
res.writeHead(e.httpCode);
strictEqual(e.code, errors.smallerThanMinFileSize);
res.end(String(e))
})
});

server.listen(PORT, () => {
const chosenPort = server.address().port;
const body = `----13068458571765726332503797717\r
Content-Disposition: form-data; name="title"\r
\r
a\r
----13068458571765726332503797717\r
Content-Disposition: form-data; name="multipleFiles"; filename="x.txt"\r
Content-Type: application/x-javascript\r
\r
\r
\r
a\r
b\r
c\r
d\r
\r
----13068458571765726332503797717--\r
`;
fetch(String(new URL(`http:localhost:${chosenPort}/`)), {
method: 'POST',

headers: {
'Content-Length': body.length,
Host: `localhost:${chosenPort}`,
'Content-Type': 'multipart/form-data; boundary=--13068458571765726332503797717',
},
body
}).then(res => {
strictEqual(res.status, 400);
server.close();
done();
});

});
});
Loading