Skip to content
Reuse the same promise that's returned from a function until it's resolved
JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
src
test
.babelrc
.editorconfig
.eslintignore
.eslintrc
.gitignore
.npmignore
.travis.yml
CHANGELOG.md
README.md
package.json

README.md

reuse-promise

build status npm version codeclimate

Purpose

TL;DR - Prevent from a unique async process (function that returns a promise) to run more than once concurrently by temporarily caching the promise until it's resolved/rejected.

When a function returns a promise and it's being called from multiple places in the app, new promises are being instantiated, and multiple async operations are going to be executed.

A common case is a function that gets an articleId and returns a promise that calls API. This function can be called from multiple places, each time will create a new promise and will issue a new request. This is usually not desired:

function findArticle(articleId) {
  return fetch(`/article/${articleId}`).then(r => r.json())
  // could also be
  // return new Promise(...)
}

// will issue first request for articleId=1
findArticle(1).then(article1 => console.log(article1))
// will issue second request for articleId=1
findArticle(1).then(article1 => console.log(article1))
// will issue first request for articleId=2
findArticle(2).then(article2 => console.log(article2))

reuse-promise decorates a function and temporary memoizes a promise until it's resolved. In this case, the first call for articleId=1 will create the new promise, issue the HTTP request, and remember that created promise for articleId=1. The second call with the same argument will return the same promise from earlier call. However, once the original promise is resolved (or rejected), a new call to findArticle(1) will issue a new request.

An initial call to a wrapped function goes through the original function, and then indexes the returned promise by a json-serialized string of the arguments that were sent to the function. So findArticles([1, 2, 3]) can be called twice and still return the same promise, becasue JSON.stringify([1, 2, 3]) === JSON.stringify([1, 2, 3]).

Installation

npm install reuse-promise --save

Usage

reuse-promise can be used as a decorator in a class definition or as a wrapper to a function.

As a class decorator

Requires babel and babel-plugin-transform-decorators-legacy plugin.

import { decorator as reusePromise } from 'reuse-promise'

class ArticleService {
  @reusePromise()
  find(articleId) {
    return fetch(`/article/${articleId}`).then(r => r.json())
  }
}

const articleService = new ArticleService()

// will issue first request for articleId=1
articleService.find(1).then(article1 => console.log(article1))
// WILL NOT issue any request for articleId=1, will reuse the promise that was created in previous call
articleService.find(1).then(article1 => console.log(article1))
// will issue first request for articleId=2
articleService.find(2).then(article2 => console.log(article2))

Wrapping a function

import reusePromise from 'reuse-promise'

function findArticle(articleId) {
  return fetch(`/article/${articleId}`).then(r => r.json())
}

const findArticleReusedPromise = reusePromise(findArticle/*, options */)


// will issue first request for articleId=1
findArticleReusedPromise(1).then(article1 => console.log(article1))
// WILL NOT issue any request for articleId=1, will reuse the promise that was created in previous call
findArticleReusedPromise(1).then(article1 => console.log(article1))
// will issue first request for articleId=2
findArticleReusedPromise(2).then(article2 => console.log(article2))

option: memoize

reuse-promise can indefinitely remember the value that was returned from a promise, so no async code will execute more than once, even if the promise was previously resolved:

import { decorator as reusePromise } from 'reuse-promise'

class ArticleService {
  @reusePromise({ memoize: true })
  find(articleId) {
    return fetch(`/article/${articleId}`).then(r => r.json())
  }
}

const articleService = new ArticleService()

articleService.find(1).then(article1 => console.log(article1))

setTimeout(() => {
  // here, the original promise is resolved
  // without memoize: true, calling find(1) would go through original function and create a promise
  // however, with memoize the following call will be immediately resolved with the value

  articleService.find(1).then(article1 => console.log(article1))
}, 1000)

Clearing all memoized values of a function can be done with:

reusePromise.clear(articleService.find)

// or
articleService.find.__reusePromise__clear()

Clear all:

reusePromise.clear()

option: serializeArguments

By default, reuse-promise indexes promises in a dictionarty where the key is all arguments JSON.stringifyied. This is sometimes an unnecessary process, especially when sending big objects as arguments.

A custom argument serializer can be provided. To reuse promises based on the first letter of the first argument, for example, provide:

@reusePromise({
  serializeArguments: args => args[0][0]
})

Or, to grab an ID of a given model without having it all serialized:

updateUserName = reusePromise(updateUserName, {
  serializeArguments: args => args[0].id
})

const someUser = { id: 1, name: 'name' }

updateUserName(someUser, 'new name')

Test

npm install
npm test

License

MIT

You can’t perform that action at this time.