I make a lot of fetches through the fetch-API in Deno TypeScript. The problem now is that randomly I get the following error (can’t be caught by try-catch):
error: Uncaught (in promise) TypeError: error sending request for url (https://www.googleapis.com/calendar/v3/calendars/****@group.calendar.google.com/events/?calendarId=****group.calendar.google.com): http2 error: stream error received: unexpected internal error encountered
const result: Response = await fetch(url, {
^
at async mainFetch (deno:ext/fetch/26_fetch.js:288:14)
at async fetch (deno:ext/fetch/26_fetch.js:505:9)
at async gFetchEvent (file:///home/****/my_script.ts:98:27)
And I don’t have any clue how to fix it. Is this a Deno bug?
I have the following deno version installed:
deno 1.25.2 (release, x86_64-unknown-linux-gnu)
v8 10.6.194.5
typescript 4.7.4
There is no particular line of code that breaks my program, just after some time (could be minutes, could be days) my program crashes with this error.
It only appears on my Ubuntu 20.04.5 LTS vServer by 1blu with the following hardware specs:
H/W path Device Class Description
=============================================
system Computer
/0 bus Motherboard
/0/0 memory 8GiB System memory
/0/1 processor AMD EPYC 7452 32-Core Processor
/1 veth09bb0e5 network Ethernet interface
/2 veth0ab53b0 network Ethernet interface
/3 veth62387d0 network Ethernet interface
/4 veth7dbc5b2 network Ethernet interface
/5 vethb66edc6 network Ethernet interface
(output of sudo lshw -short
)
The code in my main script:
try {
await main()
} catch (e) {
console.log(new Date(), e.stack)
Deno.writeTextFileSync(`logs/${Date.now()}`, "" + e.stack)
}
my main function
// this program checks changes in my school schedule and automatically puts them in my google calendar
export default async function main() {
await Kantplaner.init()
while (true) {
// MKDate is just a class that extends Date for more functionallity, nothing special
const start_day = new MKDate(Date.now())
// repeats 14 times for the next 14 days
for (let i = 0; i < 14; i++) {
const date: MKDate = i ? start_day.nextDate(1) : start_day
// get my schedule from my school's site
const vplan: VPlan | null = await Indiware(date)
if (!vplan) continue
// fetch the existing events with google calendar api and check if something in the meantime changed
const calendar = await Kantplaner.list_events(date)
// male one big object containing all indices that were previously built by `await Indiware(date)`
const GrandIndex = { ...vplan.data.KlassenIndex, ...vplan.data.LehrerIndex }
for (const item of calendar.items) {
const stundenNr = "some_string"
const stundenMitDerID = GrandIndex[stundenNr]
// if the event is not in my school's schedule anymore, delete it
if (!stundenMitDerID) {
await Kantplaner.delete_event(item.id)
continue
}
// for every other event check differences and update the corresponding Google event
// `stundenMitDerID` is an array of events with the same id (can happen at my school)
for (let i = 0; i < stundenMitDerID.length; ++i) {
// ... create update (doesn't matter)
const update: Kantplaner.Update = {}
await Kantplaner.update_event(item.id, update)
// remove lesson from index to avoid another creation
GrandIndex[stundenNr].splice(i)
if (GrandIndex[stundenNr].length == 0) delete GrandIndex[stundenNr]
}
}
// create every remainig event
for (const stundenNr in GrandIndex) {
const stundenMitDerID = GrandIndex[stundenNr]
for (let i = 0; i < stundenMitDerID.length; ++i) {
await Kantplaner.create_event({
// event data
})
}
}
}
// wait one minute to reduce unnecessary fetches
await new Promise(r => setTimeout(r, 60_000))
}
}
All appearances of gFetchEvent
:
export async function list_events(date: MKDate, TIME_SHIFT: string): Promise<Calendar> {
const calendar: Calendar | null = await gFetchEvent("/", "GET", {
timeMin: date.format(`yyyy-MM-ddT00:00:00${TIME_SHIFT}`),
timeMax: date.format(`yyyy-MM-ddT23:59:59${TIME_SHIFT}`),
})
if (!calendar) return EMPTY_CALENDAR
let nextPageToken = calendar.nextPageToken
while (nextPageToken) {
const nextPage: Calendar = await gFetchEvent("/", "GET", {
pageToken: nextPageToken,
timeMin: date.format(`yyyy-MM-ddT00:00:00${TIME_SHIFT}`),
timeMax: date.format(`yyyy-MM-ddT23:59:59${TIME_SHIFT}`),
})
calendar.items.push(...nextPage.items)
nextPageToken = nextPage.nextPageToken
}
return calendar
}
export async function create_event(event: Event) {
await gFetchEvent("/", "POST", { calendarId: CALENDAR_ID }, event)
}
export async function update_event(eventId: string, update: Update) {
await gFetchEvent(`/${eventId}`, "PATCH", {
sendUpdates: "none"
}, update)
}
export async function delete_event(eventId: string) {
await gFetchEvent(`/${eventId}`, "DELETE", {
calendarId: CALENDAR_ID,
eventId: eventId,
sendUpdates: "none"
})
}
The code where I fetch:
async function gFetchEvent(urlPath: string, method: string, params?: { [key: string]: string }, body?: any) {
if (!initiated) return null
const url = new URL(CALENDAR_API_URL + urlPath)
if (params) for (const key of Object.keys(params)) url.searchParams.append(key, params[key])
const result: Response = await fetch(url, {
headers: {
Authorization: "Bearer " + access_token,
Accept: "application/json",
"Content-Type": "application/json"
},
method: method,
body: JSON.stringify(body)
})
await new Promise(f => setTimeout(f, 100))
if (result.ok) {
const text = await result.text()
if (text.length > 1) return JSON.parse(text)
else return {}
} else if (result.status == 403) {
gFetchEvent(urlPath, method, params, body)
return
}
return null
}
Every function call like list_events
or update_event
implicitly calls gFetchEvent
function (with the different url’s and ).
2
Answers
Thanks to @Marcos Casagrande I realized that I had a call to an async function that throws an error without an await. That would not be a problem if there was no error thrown by the fetch.
But some error by fetch (most likely a network error) caused exactly this error to be thrown.
You’re missing an
await
, without it, the error won’t bubble correctly to thetry/catch
in yourmain
function.Alternatively if you don’t want to use
await
in that path, you can attach a.catch
handler togFetchEvent
to prevent theUncaught Promise
error.