Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/9oelm/async-jobs
For the paranoids of async jobs in javascript - track, manage, access all async jobs like network requests on browser at one place.
https://github.com/9oelm/async-jobs
async network
Last synced: 17 days ago
JSON representation
For the paranoids of async jobs in javascript - track, manage, access all async jobs like network requests on browser at one place.
- Host: GitHub
- URL: https://github.com/9oelm/async-jobs
- Owner: 9oelM
- License: mit
- Created: 2022-01-11T03:51:24.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2022-01-15T13:05:36.000Z (almost 3 years ago)
- Last Synced: 2024-10-14T04:17:10.408Z (about 1 month ago)
- Topics: async, network
- Language: TypeScript
- Homepage: https://9oelm.github.io/async-jobs
- Size: 6.01 MB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# async-jobs
_For the paranoids of async jobs in javascript._
- Track, manage, access all async jobs.
- Best used for multiple, long running, error-prone jobs (network requests, web workers, etc).
- Strictly typed everywhere with Typescript.
- Usable for both Redux and non-redux applications.
- Recipes included.# Install
```
npm i --save @async-jobs/core
``````
yarn add @async-jobs/core
```# Demo
https://9oelm.github.io/async-jobs/
# Full API Reference
https://async-jobs-api-docs.surge.sh/
# Usage with Redux
Before you begin, you need to create `async` reducer in your existing redux store. This is going to be the single source of truth for all of your async jobs:
```javascript
import { combineReducers, createStore } from "redux"
import { asyncReducer } from '@async-jobs/core';const rootReducer = combineReducers({
async: asyncReducer,
otherReducers: reducer1,
...
})export const store = createStore(rootReducer)
```## The 'Vanilla Redux' way
[👉 Run this example on your computer](https://github.com/9oelM/async-jobs/tree/main/packages/vanilla-redux-example)
This is the most fundamental way to use `async-jobs` in a Redux application, although it is not the recommended way. This is not recommended because side effects reside in the component but redux middleware, which means it is almost equivalent to using a local `useState`. But it does the job - it can track the async request and it is stored in redux, accessible from anywhere else too. You get the idea.
```javascript
import React, { useEffect, useRef } from "react"
import { useDispatch } from "react-redux"
import {
asyncJobByIdSelector,
AsyncStatus,
createJobSet,
} from "@async-jobs/core"
import { TcResult, tcAsync } from "./utilities/essentials"
import { useTypedSelector } from "."export class API {
static baseUrl = `https://example-api-six.vercel.app`static async bookFlightTicket({
timeout_secs = 0,
make_error = false,
}: {
timeout_secs?: number
make_error?: boolean
}): Promise> {
return tcAsync(
window
.fetch(
`${API.baseUrl}/api/main?timeout_secs=${timeout_secs}&make_error=${make_error}`
)
.then((response) => {
if (response.ok) {
return response.text()
} else {
throw new Error(`Error happened: ${response.statusText}`)
}
})
)
}
}enum AsyncJobNames {
POST_BOOK_FLIGHT_TICKET = `POST_BOOK_FLIGHT_TICKET`,
}export const postBookFlightTicketJobSet = createJobSet<
AsyncJobNames.POST_BOOK_FLIGHT_TICKET,
{
destination: string
username: string
},
{
destination: string
username: string
},
string,
Error,
void
>(AsyncJobNames.POST_BOOK_FLIGHT_TICKET)export const BookFlightPage: React.FC<{
destination: string
username: string
}> = ({ destination, username }) => {
const bookFlightTicketCreatedJob = useRef(
postBookFlightTicketJobSet.create({
payload: {
destination,
username,
},
})
)
const dispatch = useDispatch()
const currentAsyncJob = useTypedSelector((s) =>
asyncJobByIdSelector(s, bookFlightTicketCreatedJob.current.id)
)useEffect(() => {
async function bookFlightTicketOnMount() {
dispatch(bookFlightTicketCreatedJob.current)
await new Promise((resolve) => {
setTimeout(resolve, 3000)
})
dispatch(
postBookFlightTicketJobSet.start({
id: bookFlightTicketCreatedJob.current.id,
payload: bookFlightTicketCreatedJob.current.payload,
})
)
const [err, postBookFlightTicketResult] = await API.bookFlightTicket({
timeout_secs: 5,
make_error: false,
})if (!err && postBookFlightTicketResult) {
dispatch(
postBookFlightTicketJobSet.succeed({
id: bookFlightTicketCreatedJob.current.id,
payload: postBookFlightTicketResult,
})
)
} else {
dispatch(
postBookFlightTicketJobSet.fail({
id: bookFlightTicketCreatedJob.current.id,
payload: err ?? new Error(`unknown error`),
})
)
}
}
bookFlightTicketOnMount()
}, [])return (
{(() => {
switch (currentAsyncJob?.status) {
case AsyncStatus.CREATED:
returnCreated
case AsyncStatus.PENDING:
returnPending
case AsyncStatus.SUCCESS:
returnSuccess
case AsyncStatus.FAILURE:
returnFailed
case AsyncStatus.CANCELLED:
returnCancelled
default:
returnUnknown
}
})()}
)
}
```## The 'Vanilla Redux middleware' way
This method uses vanilla redux middleware without any other dependencies like `redux-thunk`. Not so many people will want to use this method because it is not very much extensible, but it is still a good option.First, create `postBookFlightTicketMiddleware`:
```js
import { AsyncJobActions, createJobSet } from '@async-jobs/core'export const AsyncJobNames = Object.freeze({
POST_BOOK_FLIGHT_TICKET: `POST_BOOK_FLIGHT_TICKET`,
})export const postBookFlightTicketJobSet = createJobSet(AsyncJobNames.POST_BOOK_FLIGHT_TICKET)
const postBookFlightTicketMiddleware = store => next => action => {
if (!isSpecificAsyncActionType(action, AsyncJobActions.START, AsyncJobNames.POST_BOOK_FLIGHT_TICKET)) {
return next(action)
}
const { payload, id } = actionAPI.bookFlightTicket({ method: `POST`, body: payload })
.then((postBookFlightTicketResult) => {
next(postBookFlightTicketJobSet.succeed({ id, payload: postBookFlightTicketResult }))
})
.catch((err) => {
next(postBookFlightTicketJobSet.fail({ id, payload: err }))
})return next(action)
}
```Then, insert the middleware into your store:
```javascript
import { combineReducers, createStore } from "redux"
import { asyncReducer } from '@async-jobs/core';
import { postBookFlightTicketMiddleware } from './postBookFlightTicketMiddleware'const rootReducer = combineReducers({
async: asyncReducer,
otherReducers: reducer1,
...
})const store = createStore(
rootReducer,
applyMiddleware(
postBookFlightTicketMiddleware,
)
)
```Now, all you have to do is to subscribe to the redux store in your component:
```js
import React from 'react'
import { useDispatch } from 'react-redux'
import { asyncJobByIdSelector } from '@async-jobs/core'const BookFlightPage = ({ destination, username }) => {
const dispatch = useDispatch()
const startPostBookFlightTicketRequest = useRef(postBookFlightTicketJobSet.start({
payload: {
destination,
username,
}
}))
const currentAsyncJob = useSelector((s) => asyncJobByIdSelector(s, startPostBookFlightTicketRequest.current.id))useEffect(() => {
dispatch(startPostBookFlightTicketRequest)
}, [])
return{(() => {
switch (currentAsyncJob.status) {
case AsyncJobStatus.PENDING:
returnPending
case AsyncJobStatus.SUCCESS:
returnSuccess
case AsyncJobStatus.FAIL:
returnFailed
case AsyncJobStatus.CANCELED:
returnCanceled
default:
returnUnknown
}
})()}
}
```Note that in some cases you may not even need to reference the async job id. All you need to know is the name of the async job. Using `createLatestAsyncJobByNameSelector` will do, like so:
```js
import React from 'react'
import { useDispatch } from 'react-redux'
import { createLatestAsyncJobByNameSelector } from "@async-jobs/core";const latestAsyncJobByNameSelector = createLatestAsyncJobByNameSelector()
// look at how clean we've become regarding the side effects
const BookFlightPage = ({ destination, username }) => {
const dispatch = useDispatch()
const currentAsyncJob = useSelector((s) => latestAsyncJobByNameSelector(s, {
name: AsyncJobNames.POST_BOOK_FLIGHT_TICKET,
}))useEffect(() => {
dispatch(postBookFlightTicketJobSet.start({
payload: {
destination,
username,
}
}))
}, [
destination,
username,
])
return{(() => {
switch (currentAsyncJob.status) {
case AsyncJobStatus.PENDING:
returnPending
case AsyncJobStatus.SUCCESS:
returnSuccess
case AsyncJobStatus.FAIL:
returnFailed
case AsyncJobStatus.CANCELED:
returnCanceled
default:
returnUnknown
}
})()}
}
```## The `redux-thunk` way
WIP
# Typescript
`async-jobs` is fully made with an obsession with Typescript. Autocompletion will help you get things right.
For more, please see full API reference.