From aa23b3906c46792a90c990f45fe14795c32a629a Mon Sep 17 00:00:00 2001 From: James Ravenscroft Date: Wed, 1 Feb 2023 09:41:08 +0000 Subject: [PATCH] added async promises post --- .../2023/02/async-promise-constructors.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 brainsteam/content/posts/2023/02/async-promise-constructors.md diff --git a/brainsteam/content/posts/2023/02/async-promise-constructors.md b/brainsteam/content/posts/2023/02/async-promise-constructors.md new file mode 100644 index 0000000..3a4cfcf --- /dev/null +++ b/brainsteam/content/posts/2023/02/async-promise-constructors.md @@ -0,0 +1,76 @@ +--- +title: "Async Promise Constructors" +date: 2023-02-01T09:39:03Z +description: Why you shouldn't use async in your new Promise() +url: /2023/2/1/async-promise-constructors +type: post +mp-syndicate-to: +- https://brid.gy/publish/mastodon +- https://brid.gy/publish/twitter +tags: + - nodejs + - typescript + - javascript + - softeng + - +--- + + +I ran into an interesting typescript/js problem yesterday at work. + +The following code snippet was generating an error and a stack trace that was never being propagated up to the caller: + +```typescript +return new Promise(async (resolve, reject) =>{ + + const data = await this.client.getObject(bucket, path) + + const buf : any[] = [] + + data.on('data', (data) => { + buf.push(data) + }) + + data.on('error', (err) => { + reject(err) + }) + + data.on('end', ()=>{ + resolve(JSON.parse(Buffer.concat(buf).toString())) + }) + +}); +``` + +The code basically uses [minio](https://min.io/)'s client library to get the contents of an object in an S3 storage bucket and then wraps the stream that it returns in a promise that will eventually parse the content of the S3 object into a JS object or reject if something went wrong. + +Seasoned js folks may have already spotted the problem (or at least guessed it based on the blog post title). + +I'ved used an `async` function inside my Promise constructor. I'm doing this so that I can make my async call to S3 but this has the added side effect of turning my promise constructer itself into a promise (promise-ception?). Promise constructors can't `await` so effectively if the `getObject` async call fails, the error is lost and nothing happens and the outer promise is never resolved or rejected. + +As it turns out, [eslint has a rule for this](https://eslint.org/docs/latest/rules/no-async-promise-executor) which I had turned off. + +### Solution + +I could make the getObject outer function async and then do the `getObject()` call in the outer layer: + +```typescript + +async function someFunction(){ + + const data = await this.client.getObject(bucket, path) + + return new Promise((resolve, reject) =>{ + const buf : any[] = [] + + data.on('data', (data) => { buf.push(data) }) + data.on('error', (err) => { reject(err) }) + data.on('end', ()=>{ + resolve(JSON.parse(Buffer.concat(buf).toString())) + }) + }); +} + +``` + +Now if the `getObject` call fails the whole `someFunction()` fails (effectively the promise that is generated by use of the await/async syntactic sugar is rejected). \ No newline at end of file