brainsteam.co.uk/brainsteam/content/posts/2023/02/async-promise-constructors.md

76 lines
2.5 KiB
Markdown
Raw Normal View History

2023-02-01 09:41:08 +00:00
---
2023-07-09 11:34:44 +01:00
date: 2023-02-01 09:39:03+00:00
2023-02-01 09:41:08 +00:00
description: Why you shouldn't use async in your new Promise()
mp-syndicate-to:
2024-10-28 20:59:46 +00:00
- https://brid.gy/publish/mastodon
- https://brid.gy/publish/twitter
2023-07-09 11:34:44 +01:00
post_meta:
2024-10-28 20:59:46 +00:00
- date
preview: /social/2dc42dc49c5694f51e252826163f1b799e811664bd8d7defc90ce9e01af09aad.png
2023-02-01 09:41:08 +00:00
tags:
2024-10-28 20:59:46 +00:00
- nodejs
- typescript
- javascript
- softeng
2023-07-09 11:34:44 +01:00
title: Async Promise Constructors
type: posts
url: /2023/2/1/async-promise-constructors
2023-02-01 09:41:08 +00:00
---
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
2024-09-04 20:56:18 +01:00
return new Promise(async (resolve, reject) => {
const data = await this.client.getObject(bucket, path);
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
const buf: any[] = [];
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
data.on("data", (data) => {
buf.push(data);
});
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
data.on("error", (err) => {
reject(err);
});
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
data.on("end", () => {
resolve(JSON.parse(Buffer.concat(buf).toString()));
});
2023-02-01 09:41:08 +00:00
});
```
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).
2024-09-04 20:56:18 +01:00
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.
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
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.
2023-02-01 09:41:08 +00:00
### Solution
I could make the getObject outer function async and then do the `getObject()` call in the outer layer:
```typescript
2024-09-04 20:56:18 +01:00
async function someFunction() {
const data = await this.client.getObject(bucket, path);
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
return new Promise((resolve, reject) => {
const buf: any[] = [];
2023-02-01 09:41:08 +00:00
2024-09-04 20:56:18 +01:00
data.on("data", (data) => {
buf.push(data);
2023-02-01 09:41:08 +00:00
});
2024-09-04 20:56:18 +01:00
data.on("error", (err) => {
reject(err);
});
data.on("end", () => {
resolve(JSON.parse(Buffer.concat(buf).toString()));
});
});
2023-02-01 09:41:08 +00:00
}
```
2024-10-28 20:59:46 +00:00
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).