Skip to content

Commit 137e6d9

Browse files
feat: implement a default notion api retry logic
this is necessary since notion seems to throw a lot more 502, timeouts and rate limit errors as of late.
1 parent 3cdc13a commit 137e6d9

File tree

1 file changed

+70
-7
lines changed

1 file changed

+70
-7
lines changed

src/NotionApiFacade.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
import { Client } from '@notionhq/client';
1+
import {
2+
APIErrorCode, APIResponseError, Client, RequestTimeoutError, UnknownHTTPResponseError
3+
} from '@notionhq/client';
24
import { DatabasesQueryParameters } from '@notionhq/client/build/src/api-endpoints';
35

6+
const debug = require("debug")("notion-api");
7+
48
/**
59
* A common facade for interaction with notion API and handling common concerns (esp. retry on 502/rate limits).
610
*
@@ -16,13 +20,17 @@ export class NotionApiFacade {
1620
}
1721

1822
async retrieveDatabase(databaseId: string) {
19-
return await this.client.databases.retrieve({
20-
database_id: databaseId,
21-
});
23+
return await withRetry(async () =>
24+
this.client.databases.retrieve({
25+
database_id: databaseId,
26+
})
27+
);
2228
}
2329

2430
async queryDatabase(query: DatabasesQueryParameters) {
25-
const result = await this.client.databases.query(query); // todo: paging
31+
const result = await withRetry(
32+
async () => await this.client.databases.query(query)
33+
); // todo: paging
2634

2735
if (result.next_cursor) {
2836
throw new Error(
@@ -34,11 +42,18 @@ export class NotionApiFacade {
3442
}
3543

3644
async retrievePage(pageId: string) {
37-
return await this.client.pages.retrieve({ page_id: pageId });
45+
return await withRetry(
46+
async () => await this.client.pages.retrieve({ page_id: pageId })
47+
);
3848
}
3949

4050
async listBlockChildren(blockId: string) {
41-
const result = await this.client.blocks.children.list({ block_id: blockId }); // todo: paging here?
51+
const result = await withRetry(
52+
async () =>
53+
await this.client.blocks.children.list({
54+
block_id: blockId,
55+
})
56+
); // todo: paging here?
4257

4358
if (result.next_cursor) {
4459
throw new Error(
@@ -49,3 +64,51 @@ export class NotionApiFacade {
4964
return result;
5065
}
5166
}
67+
68+
69+
function sleep(ms: number) {
70+
return new Promise(resolve => setTimeout(resolve, ms));
71+
}
72+
73+
async function withRetry<T>(
74+
f: () => Promise<T>,
75+
maxRetries: number = 3,
76+
retriableApiErrorCodes: APIErrorCode[] = [
77+
APIErrorCode.ServiceUnavailable,
78+
APIErrorCode.RateLimited,
79+
],
80+
retriableUnknownHTTPStatusCodes: number[] = [502]
81+
) {
82+
let lastError: any;
83+
84+
for (let i = 1; i <= maxRetries; i++) {
85+
try {
86+
return await f();
87+
} catch (error: any) {
88+
lastError = error;
89+
const isRetriable =
90+
(APIResponseError.isAPIResponseError(error) &&
91+
error.code in retriableApiErrorCodes) ||
92+
(UnknownHTTPResponseError.isUnknownHTTPResponseError(error) &&
93+
error.status in retriableUnknownHTTPStatusCodes) ||
94+
(RequestTimeoutError.isRequestTimeoutError(error));
95+
96+
if (!isRetriable) {
97+
// throw any other error immediately
98+
throw error;
99+
}
100+
101+
debug(
102+
`Notion API request failed with error ${error.code}, ${
103+
maxRetries - i
104+
} retries left`
105+
);
106+
107+
await sleep(1000 * i); // chosen by fair dice roll
108+
}
109+
}
110+
111+
throw new Error(
112+
`Failed to execute Notion API request, even after ${maxRetries} retries. Original error was ${lastError}`
113+
);
114+
}

0 commit comments

Comments
 (0)