npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

synchronous-promise-stack-resolver

v1.1.2

Published

Synchronously resolves promises using localForage (not tested) or react native's asyncStorage if needed and any kind of event dispatcher

Downloads

7

Readme

Synchronous Promise Stack Resolver

Synchronously resolves promises using react native's AsyncStorage (tested) or LocalForage (not tested) for data persistence if needed and any kind of event dispatcher

Installation

npm install --save synchronous-promise-stack-resolver

Then import it in your code:

import PromiseStackResolver from 'synchronous-promise-stack-resolver';

What's the purpose of this package ?

Initially, this package was built for a personal purpose. I was building an offline first React Native app and wanted to synchronize user data to a server. On each user CRUD action I had to store the parameters of the action and replay them as POST, PUT, DELETE requests later on in the same order as they came in the first place. I also had to handle each response differently according to its nature or status code. Additionaly I also needed to send events before and after everything was resolved.

How does it work ?

Once configured and initialized, the PromiseStackResolver can be used in different ways. You can either call start() and it will try to resolve everything it has in its stack (gotten from storage or memory) on every tick (see configuration section), or you can just call processStack whenever you need. You add the params for a promise using addItem(item) and the promise is created using the createPromiseCaller function you provide via the configuration object. You also have to provide an handleError function if you want to take specific action in case of Promise rejection.

Configuration

Constructor

The PromiseStackResolver's constructor optionnaly takes an implementation of asynchronous storage as first argument (React Native's AsyncStorage or browser's localForage for example) that must implement async methods getItem, setItem and removeItem. As a second argument you may provide an event dispatcher of your choice that must implement a dispatch method.

Configuration

The init method takes an object as only argument which must contain some configuration data, here is the list:

  • createPromiseCaller: function returning function that returns a Promise object (see below),
  • useEventDispatcher (optional): boolean (default: false),
  • useAsyncStorage (optional): boolean (default: false),
  • pendingPromiseParamsListKey (required if useAsyncStorage is true): string (default: null),
  • secondaryPendingPromiseParamsListKey (required if useAsyncStorage is true): string (default: null),
  • storeIndex (optional): boolean (default: false),
  • indexKey (required if useAsyncStorage and storeIndex are true): string (default: null),
  • updateAsyncStorageIntervalLength (optional) : number (milliseconds) (default: null),
  • processPromiseStackIntervalLength (optional): number (milliseconds) (default: null),
  • getProcessStackStartEventList (optional): function returning array of event objects to be dispatched by the eventDispatcher provided to the constructor (default: []),
  • getProcessStackEndEventList (optional): function returning array of event objects to be dispatched by the eventDispatcher provided to the constructor (default: []),
  • shouldProcessStack (optional): function returning boolean (default: () => true),
  • handleError (optional): function returning Promise (default: () => Promise.resolve())

boolean keys are used to specify if you wish to use specific functionality, for example if useEventDispatcher is set to true, the processStack method will trigger all the events contained in the array returned by the getProcessStackStartEventList provided function when it starts, and all the events contained in the array returned by the getProcessStackEndEventList provided function when it ends. It will not work if you did not provide an eventDispatcher at construct.

Following the same idea, useAsyncStorage will persist pendingPromise params into storage to be able to take back where it stopped on next launch of your app. useAsyncStorage requires that you provided an async storage implementation at contruct and pendingPromiseParamsListKey and secondaryPendingPromiseParamsListKey params in config.

storeIndex will persist an index that you can access in createPromiseCaller function as well as handleError, it will be enabled only if you provide an indexKey as well and you have provided an implementation of async storage at contruct.

string keys are used for storage and can be anything. Usually you will want to make them unique if you're persisting things for multiple users for example.

number keys are used to set intervals, one for processRequestStack, one for updateAsyncStorage so that it calls these functions every N milliseconds. (NB updateAsyncStorage is also called after every resolved promise)

createPromiseCaller(pendingPromiseParams, getIndexItem, setIndexItem, eventDispatcher): this function is called to create a promise caller function for each item stored in the stack. It takes arguments that you might want to use to return your promise. The pendingPromiseParams is used to return a specific promise caller, the get/setIndexItem methods are used to keep and use things from a custom index (js object) during processStack (example: revision number of an updated entity since when you saved it into params it might have had an old revision number, see example below). The eventDispatcher is there in case you might want to dispatch specific events.

handleError(error, pendingPromiseParamsList, getIndexItem, setIndexItem, cancel, eventDispatcher): this function is called upon error (aka promise rejection), it takes the error as first argument so you can take action according to its type. pendingPromiseParamsList in case you might need to update it, get/setIndexItem (see above), cancel which is a function that will cancel all following promises (but not unstack them) and eventDispatcher in case you might want to dispatch specific events.

Helper methods

A number of helper methods are available on the instance of PromiseStackResolver:

  • isSynced(): returns true when processing is finished and stack is empty
  • getStackSize(): returns the number of items contained in pendingPromiseParamsList
  • getSecondaryStackSize(): returns the number of items contained in secondaryPendingPromiseParamsList (this secondaryStack is used when addItem(item) is called while processStack is running, it is merged in normal stack after processStack is finished)
  • getLastProcessingErrorCount(): returns the count of errors found during last call to processStack
  • getLastProcessingErrorList(): returns an array of the errors found during last call to processStack
  • stop(): stops the automatic calls to processStack and updateAsyncStorage end releases resources
  • isProcessing(): returns a boolean indicating if processStack is actually running or not
  • clearStorage(): removes all data contained in storage corresponding to config's keys
  • getStatus(): returns the status of the PromiseStackResolver
  • requestStorageUpdate(): request updating storage next time updateAsyncStorage is called
  • revokeStorageUpdate(): revoke updating storage next time updateAsyncStorage is called

Example

const createPromiseCaller = (
  pendingPromiseParams, // this contains the object you passed to addItem
  getIndexItem,
  setIndexItem,
  eventDispatcher
) => {
  // let's assume your pendingPromiseParams has a type prop
  switch (pendingPromiseParams.type) {
    case 'POST':
      // retrun a function that returns a promise
      return () => {
        // dispatch any kind of event
        const beforePostEvent = {};
        eventDispatcher.dispatch(beforePostEvent);
        // let's assume your pendingPromiseParams has a url prop and a body props
        return request.post(pendingPromiseParams.url, pendingPromiseParams.body)
          .then(response => response.json())
          .then(responseBody => {
            // let's assume your responseBody has an id and a rev props
            // and you want to keep track of the rev to update the same entity later
            setIndexItem(responseBody.id, responseBody.rev);
            // dispatch any kind of event
            const afterPostEvent = {};
            eventDispatcher.dispatch(afterPostEvent);
            return responseBody;
          });
    }
    case 'PUT':
      return () => {
        const beforePutEvent = {};
        eventDispatcher.dispatch(beforePutEvent);
        // get the currentRev from the index
        const currentRev = getIndexItem(pendingPromiseParams.body.id);
        if (currentRev && pendingPromiseParams.body.rev < currentRev) {
          // update entity with current rev
          pendingPromiseParams.body.rev = currentRev;
        }
        return request.put(pendingPromiseParams.url, pendingPromiseParams.body)
          .then(response => response.json())
          .then(responseBody => {
            setIndexItem(responseBody.id, responseBody.rev);
            const afterPutEvent = {};
            eventDispatcher.dispatch(afterPutEvent);
            return responseBody;
          })
        ;
      }
    default:
      return () => Promise.resolve();
  }
}

const handleError = (
  error,
  pendingPromiseParamsList,
  getIndexItem,
  setIndexItem,
  cancel,
  eventDispatcher
) => {
  switch (error.name) {
    case 'ConflictError':
      const errorEvent = {};
      eventDispatcher.dispatch(errorEvent);
      pendingPromiseParamsList.shift();
      break;
    default:
      const cancelEvent = {};
      eventDispatcher.dispatch(errorEvent);
      cancel();
      break;
  }
}

const config = {
  storeIndex: true,
  useEventDispatcher: true,
  useAsyncStorage: true,
  pendingPromiseParamsListKey: 'pending_promise_params_list',
  secondaryPendingPromiseParamsListKey: 'secondary_pending_promise_params_list',
  indexKey: 'revision_index',
  updateAsyncStorageIntervalLength: 3000,
  processPromiseStackIntervalLength: 3000,
  getProcessStackStartEventList: () => [{}, {}], // any events
  getProcessStackEndEventList: () => [{}, {}, {}], // any events
  shouldProcessStack: () => true, // any function, if it returns true processStack will proceed
  handleError,
  createPromiseCaller,
}

const promiseStackResolverInstance = new PromiseStackResolver(anyAsyncStorage, anyEventDispatcher);
promiseStackResolverInstance.init(config) // initialize with config object
  .then(() => {
    promiseStackResolverInstance.start() // start will set the intervals to processStack and updateAsyncStorage
      .then(() => {
        let entity = { id: 1, rev: 0, name: 'foo' }; // create an entity
        // add POST entity to stack
        promiseStackResolverInstance.addItem({ type: 'POST', url: 'https://api.example.com/entity', body: entity});
        entity.name = 'bar'; // update same entity
        // add PUT entity to stack
        promiseStackResolverInstance.addItem({ type: 'PUT', url: 'https://api.example.com/entity', body: entity});
      })
  })
;