Skip to content

Instantly share code, notes, and snippets.

@borispoehland
Last active August 23, 2023 21:11
Show Gist options
  • Save borispoehland/b48331c186c0c287f7debd1103b04e36 to your computer and use it in GitHub Desktop.
Save borispoehland/b48331c186c0c287f7debd1103b04e36 to your computer and use it in GitHub Desktop.
const getAccessToken = fetchFresh<{ access_token: string }>(
null,
'https://accounts.spotify.com/api/token',
{
method: 'POST',
headers: {
Authorization: `Basic ${Buffer.from(
`${process.env.SPOTIFY_CLIENT_ID}:${process.env.SPOTIFY_CLIENT_SECRET}`
).toString('base64')}`,
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: String(process.env.SPOTIFY_REFRESH_TOKEN),
}),
}
)
// Generally, in an Effect<R, E, A>, when E is defined (like below FetchError | JSONError | ParseError), the promise
// can reject when we run it
// Effect<never, FetchError | JSONError | ParseError, ISong>
const program1 = pipe(
getAccessToken,
Effect.flatMap(({ access_token }) => {
return fetchFresh<{ item: ISong }>(
null,
'https://api.spotify.com/v1/me/player/currently-playing',
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
)
}),
Effect.map(({ item }) => item)
)
// this can reject, because E is FetchError | JSONError | ParseError
try {
await Effect.runPromise(program1)
} catch (error) {
// kinda bad, because error is unknown again. We had type safety but lost it by not catching the errors on the effect
console.log(error)
}
// FetchError is gone!
// Effect<never, JSONError | ParseError, ISong | null>
const program2 = pipe(
getAccessToken,
Effect.flatMap(({ access_token }) => {
return fetchFresh<{ item: ISong }>(
null,
'https://api.spotify.com/v1/me/player/currently-playing',
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
)
}),
Effect.map(({ item }) => item),
// better already! We explicitly handle the fetch error by logging it and returning null
Effect.catchTag('FetchError', () => {
return Effect.succeed(null).pipe(
// tap essentially executes the command within, but doesn't set it as new result. Hence the return type here is
// null (from Effect.succeed(null)) instead of void (from Effect.log(error))
Effect.tap((error) => Effect.log(error))
)
})
)
// This can still reject, since E is JSONError | ParseError. But FetchError has been handled properly at least
try {
await Effect.runPromise(program2)
} catch (error) {
console.log(error)
}
// All errors gone!
// Effect<never, never, ISong | null>
const program3 = pipe(
getAccessToken,
Effect.flatMap(({ access_token }) => {
return fetchFresh<{ item: ISong }>(
null,
'https://api.spotify.com/v1/me/player/currently-playing',
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
)
}),
Effect.map(({ item }) => item),
// thanks to this we basically say: Whatever error happens, when it happens, return null as value
// hence the new return type is ISong | null, while error type is never
Effect.orElseSucceed(() => null)
)
// Promise can not reject anymore, because E is never, because we handled our errors via Effect.orElseSucceed(() => null)
// no try-catch necessary anymore
await Effect.runPromise(program2)
// Super interesting. We basically lift the error from the Error spot into the value spot, so that the promise will never reject
// This gives us high flexibility over what we want to do in case an error or success happens, but we have full type safety
// Effect<never, never, Either<FetchError | JSONError | ParseError, ISong>
const program4 = pipe(
getAccessToken,
Effect.flatMap(({ access_token }) => {
return fetchFresh<{ item: ISong }>(
null,
'https://api.spotify.com/v1/me/player/currently-playing',
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
)
}),
Effect.map(({ item }) => item),
Effect.either
)
// can not reject because E of program4 is never
// no try-catch needed either
const result = await Effect.runPromise(program4)
// ISong | null
const successResult = result._tag === 'Right' ? result.right : null
// FetchError | JSONError | ParseError | null
const errorResult = result._tag === 'Left' ? result.left : null
// Effect<never, FetchError | JSONError | ParseError, ISong>
const program5 = pipe(
getAccessToken,
Effect.flatMap(({ access_token }) => {
return fetchFresh<{ item: ISong }>(
null,
'https://api.spotify.com/v1/me/player/currently-playing',
{
headers: {
Authorization: `Bearer ${access_token}`,
},
}
)
}),
Effect.map(({ item }) => item)
)
// Another very interesting method, notice the "runPromiseExit"
// Exit<FetchError | JSONError | ParseError, ISong>
const exit = await Effect.runPromiseExit(program5)
Exit.match(exit, {
onSuccess: (song) => `Song is ${song.name}`,
onFailure: (cause) => console.log(cause),
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment