Skip to content

Commit

Permalink
feat: Promise (#940)
Browse files Browse the repository at this point in the history
* feat: promise

* doc: change example to use promise

* test: add test for promise

* chore: version and changelog

* doc: add promise
  • Loading branch information
GrosSacASac committed Jun 17, 2023
1 parent c249922 commit e4f29e7
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 41 deletions.
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)) {
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();
});

});
});

0 comments on commit e4f29e7

Please sign in to comment.