{"id":4697,"url":"https://github.com/Gabrn/react-native-persistent-job","last_synced_at":"2025-08-04T02:31:29.424Z","repository":{"id":57339033,"uuid":"98332286","full_name":"Gabrn/react-native-persistent-job","owner":"Gabrn","description":"Run async tasks that retry after a crash, connection loss or exception","archived":false,"fork":false,"pushed_at":"2018-05-26T08:26:28.000Z","size":243,"stargazers_count":80,"open_issues_count":1,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-08-16T18:56:34.326Z","etag":null,"topics":["javascript","react-native","storage"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Gabrn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-25T17:24:09.000Z","updated_at":"2023-11-14T03:10:49.000Z","dependencies_parsed_at":"2022-09-10T18:10:30.597Z","dependency_job_id":null,"html_url":"https://github.com/Gabrn/react-native-persistent-job","commit_stats":null,"previous_names":["gabinir/react-native-persistent-job"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabrn%2Freact-native-persistent-job","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabrn%2Freact-native-persistent-job/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabrn%2Freact-native-persistent-job/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Gabrn%2Freact-native-persistent-job/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Gabrn","download_url":"https://codeload.github.com/Gabrn/react-native-persistent-job/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228587347,"owners_count":17941442,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","react-native","storage"],"created_at":"2024-01-05T20:17:20.582Z","updated_at":"2024-12-07T09:30:53.822Z","avatar_url":"https://github.com/Gabrn.png","language":"TypeScript","funding_links":[],"categories":["Components"],"sub_categories":["Storage"],"readme":"# react-native-persistent-job \n  \n[![NPM version](https://img.shields.io/npm/v/react-native-persistent-job.svg)](https://www.npmjs.com/package/react-native-persistent-job)\n[![build status](https://travis-ci.org/Gabrn/react-native-persistent-job.svg?branch=master)](https://travis-ci.org/Gabrn/react-native-persistent-job)\n* Run parametized asynchronous functions (called `jobs`) that will re-run in cases of failure, connection loss, application crash or user forced shutdowns.  \n* \u003cb\u003eRuns on both android and iOS.\u003c/b\u003e  \n\n## Why?\n\nWhen you develop an application you usually focus on the 'happy flow', where everything runs smoothly, the user has perfect connection, he only leaves the app through the exit button, the app never crashes and your backend or any of the services you use never fail.   \nWell... it is not usually like that.   \nThings will fail and might leave your application in an unstable state.  \nThis repository aims to help deal with that. \n\n## Installation\n\nInstall package from npm\n\n```sh\nnpm install --save react-native-persistent-job\n```\n\n\n## Usage\n\nThere are 2 main apis. One for initialization, registering job types and applying modifiers (`initializeStore`) and one for running jobs (`createJob`).\n\n### `initializeStore`\n`initializeStore` is used to set up options \u0026 run stored jobs.\nAs soon as `initializeStore` is called the stored jobs that did not finish execution run.  \nTo run any kind of persistent job you must call `initializeStore` first.  \n  \n\u003cb\u003eArguments\u003c/b\u003e:  \n* `storeName?: string` - optional store name that identifies your instance when you call createJob\n* `jobHandlers: Array\u003c{jobType: string, handleFunction: (...args: any) =\u003e Promise\u003cvoid\u003e}\u003e` - an array of job type (the key that identifies the job) and an handle function to run when the job type is called\n* `modifyJobStream?: Rx.Observable\u003cJobNumbered\u003e =\u003e RxObservable\u003cJobNumbered\u003e` - Modifies the stream that runs the jobs (more on that later in the readme)\n* `modifyRetryStream?: Rx.Observable\u003cJobNumbered\u003e =\u003e RxObservable\u003cJobNumbered\u003e` - Modifies the stream that retries the jobs (more on that later in the readme)\n* `concurrencyLimit?: number` - Limit the number of jobs that can run concurrently, other jobs will have to wait until the running jobs finish before they can run.\n\n### `createJob`\n`createJob` is used to run the jobs, it accepts the job type as an argument and returns the function of that job type wrapped with persistent-job functionality.\nFor example if I want to run a function `(a, b, c) =\u003e console.log(a, b, c)` that has a jobType `console` I will run it like this: `persistentJob.store().createJob('console')('valueForA', 'valueForB', 'valueForC')`    \n  \n\u003cb\u003eArguments\u003c/b\u003e:   \n* `jobType: string` - the job type for the job you want to run that you registered beforehand in `initializeStore` with the `jobHandlers` param\n* `...args: any[]` - the args that the function accepts\n\n### example for `initializeStore` and `createJob`\n\n```js\nimport persistentJob from 'react-native-persistent-job'\n\nconst logIt = (a, b, c) =\u003e console.log(a, b, c)\n\nawait persistentJob.initializeStore({\n\tjobHandlers: [{jobType: 'logIt', handleFunction: logIt}]\n})\n\nconst persistentLogIt = persistentJob.store().createJob('logIt')\npersistentLogIt('valueForA', 'valueForB', 'valueForC')\npersistentLogIt('AnotherValueForA', 'AnotherValueForB', 'AnotherValueForC')\n```\n\n## Job \u0026 Retry streams\nThe jobs run on a stream that can be modified using rx. There are some built-in modifiers which can be used to modify the stream.\n\n### `runWhenOnline`\nWill only run the jobs once the device is connected to the internet.\n\n* example\n```js\nimport persistentJob, {streamModifiers} from 'react-native-persistent-job'\n\nconst logIt = (a, b, c) =\u003e console.log(a, b, c)\n\nawait persistentJob.initializeStore({\n\tstoreName: 'online-jobs',\n\tjobHandlers: [{jobType: 'logIt', handleFunction: logIt}]\n\tmodifyJobStream: streamModifiers.runWhenOnline\n})\n\nconst persistentLogIt = persistentJob.store('online-jobs').createJob('logIt')\npersistentLogIt('valueForA', 'valueForB', 'valueForC')\n```\n\n### `withBackoff`\nWill run failed jobs with delay based on a backoff method (right now either exponential or fibonacci)\n\n* Api:\n```js\nstreamModifiers.retryStream.withBackoff.{exponential / fibonacci}(initialWaitTime: number, maxWaitTime?: number)\n```\n\n* Example:\n\n```js\nimport persistentJob, {streamModifiers} from 'react-native-persistent-job' \n\nconst failureOfAJob = (msg) =\u003e {\n\tconsole.log(msg)\n\tthrow 'I failed'\n}\n\nconst failingJobsStore = await persistentJob.initializeStore({\n\tstoreName: 'failing-jobs',\n\tjobHandlers: [{jobType: 'failureOfAJob', handleFunction: failureOfAJob}]\n\tmodifyRetryStream: streamModifiers.retryStream.withBackoff.exponential(10, 50)\n})\n\nconst persistentFailureOfAJob = failingJobsStore.createJob('failureOfAJob') \npersistentFailureOfAJob('I will fail while running')\n```\n\n## Stateful \u0026 stateless jobs\nUsually you will want to use stateless jobs.  \nSometimes however, it is more convenient for a job to have a state, then whenever it runs after a failure it will remember the last state it was at.  \nTo do that your function will have to have a prefix with 2 arguments `currentState` and `updateState`.  \nHere is an example with both a stateless and a stateful job:\n```js\nconst statelessJob = async (name) =\u003e {\n\tfor (let i = 0; i \u003c 10; i++) {\n\t\tconsole.log('Hello name', i)\n\t}\n}\n\nconst statefulJob = (currentState, updateState) =\u003e async (name) =\u003e {\n\tconst start = currentState || 0\n\tfor (let i = start; i \u003c 10; i++) {\n\t\tconsole.log('Hello name', i)\n\t\tawait updateState(i)\n\t}\n}\n\nawait persistentJob.initializeStore({\n\tstoreName: 'stateless-stateful',\n\tjobHandlers: [\n\t\t{jobType: 'stateless', handleFunction: statelessJob},\n\t\t{jobType: 'stateful', handleFunction: statefulJob, isStateful: true}\n\t]\n})\n\nconst persistentStatelessJob = persistentJob.store('stateless-stateful').createJob('stateless')\nconst persistentStatefulJob = persistentJob.store('stateless-stateful').createJob('stateful')\npersistentStatelessJob('john')\npersistentStatefulJob('mary')\n```\n\n## jobHandlerModifiers\n### `limitJobRuns`\nLimits the number of possible failures a particular job can have before it is terminated\n* example\n```js\nimport persistentJob, {jobHandlerModifiers} from 'react-native-persistent-job'\n\nconst logIt = (a, b, c) =\u003e console.log(a, b, c)\n\nawait persistentJob.initializeStore({\n\tjobHandlers: [\n\t\tjobHandlerModifiers.limitJobRuns(3)({jobType: 'logIt', handleFunction: logIt}) // here we modify the handler to run failing jobs only 3 times max.\n\t]\n})\n\nconst persistentLogIt = persistentJob.store().createJob('logIt')\npersistentLogIt('valueForA', 'valueForB', 'valueForC') // will only run 3 times\n```\n\n## Subscriptions \nMany times when async operations are running it's good to give some sort of indication for better user experience.\nLike for example if we made an http request that fetches some data for the user we might want to show a spinner that indicated the request is still active.\nThe most convenient way to do that is with subscriptions (calling the state management library from within the jobs is also an option but not as convenient).\nTo subscribe to a job we must first give the job a `topic`. Like this: \n```js\nconst jobWithTopic = persistentJob.store().createJob('myJob', 'some_topic')\njobWithTopic()\n```\n\nThen we can subscribe to the topic like this (it is also possible to subscribe to a topic before we run the job, the first value we will receive is JOB_NOT_FOUND though, after that we will receive the rest of the states):\n```js\n// subscribing the job\nconst unsubscribe = persistentJob.store().subscribe('some_topic', (jobTopicOutput) =\u003e {\n\tconsole.log('do something')\n})\n\n// to unsubscribe\nunsubscribe()\n```\n\n* Subsciptions will also work after an application crash, meaning that if we subscribe to a job that failed in the last application run the subscription will still get data from it.\n\n* The subscription function returns a function to unsubscribe\n\n* When the subscription function is called it is passed an object of the following structure: \n```js\n{jobState: 'JOB_STARTED' | 'JOB_DONE' | 'JOB_NOT_FOUND' | 'JOB_INTERMEDIATE' | 'JOB_FAILED', value?: any}\n```\nEach jobState meaning is displayed in the `console.log` functions in this example:\n\n```js \nconst unsubscribe = persistentJob.store().subscribe('some_topic', (jobTopicOutput) =\u003e {\n\tif (jobTopicOutput.jobState === 'JOB_STARTED') console.log('job is just starting')\n\tif (jobTopicOutput.jobState === 'JOB_DONE') {\n\t\tconsole.log('job finished in this current application run')\n\t\tconsole.log(`This is the value the job finished with: ${jobTopicOutput.value}`)\n\t} \n\tif (jobTopicOutput.jobState === 'JOB_NOT_FOUND') console.log('job finished in some other application run or was never started')\n\tif (jobTopicOutput.jobState === 'JOB_INTERMEDIATE') {\n\t\tconsole.log('Only stateful jobs have intermediate state, it also has a value which is the current state of the stateful job')\n\n\t\tconsole.log(`This is the intermediate state value: ${jobTopicOutput.value}`)\n\t}\n\n\tif (jobTopicOutput.jobState === 'JOB_FAILED') {\n\t\tconsole.log('Job failed in this application run, it might restart soon though')\n\n\t\tconsole.log(`This is the error ${jobTopicOutput.value}`)\t\t\n\t} \n})\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGabrn%2Freact-native-persistent-job","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGabrn%2Freact-native-persistent-job","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGabrn%2Freact-native-persistent-job/lists"}