question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add Promise support

See original GitHub issue

Working on a version of this on my fork now but it would be great to be able to pass in a promise and have the library return a promise that resolved to that result.

ie:

var foo = memoize(myPromiseReturningFunc);

foo(a, b, c).then((data) => {
    // do something with the data...
});

Per JS spec the official way of doing this check would be typeof fn.then == "function" as that makes the function a “thenable”.

Thoughts, concerns, etc? I may open a PR if I get it in fast enough 😉

Issue Analytics

  • State:open
  • Created 7 years ago
  • Reactions:4
  • Comments:17 (7 by maintainers)

github_iconTop GitHub Comments

2reactions
caiogondimcommented, Nov 17, 2016

BTW, let’s work together on the next major version =)

Will create a document and share with you Once we are happy with the architecture, we can start coding

The main idea is to give a function with as less checking as possible based on hints given by user If user don’t give any hint, we have to use a generic function

0reactions
JasonKlebancommented, Feb 10, 2020

Here’s a workaround with the current library (in typescript). Half to help out and half for peer review in case I missed something. The serializer seems to assume a string hash, but I needed access to the arguments for the retry case, so it requires a little bit of a hack.

import stableStringify from "fast-json-stable-stringify";
import memoize from "fast-memoize";

// A bit of a hack
interface CacheArgsAndHash { args: any[], hash: string }

// Since has/get/set are unavoidably synchronous, and since 
// there is no synchronous test for Promise state, we leverage 
// the returned promise to perform the test and retry if necessary.  
// This also lets multiple initial attempts during the pending 
// phase to share the result.  Rejected attempts are not retried, 
// but later attempts will see the rejection and attempt the cache *that*.
class PromiseAwareMemoizingCache<T extends (...args: any[]) => any> {
  private store: { [key : string]: any } = {};

  constructor(private fn : T) { }

  has = (argsAndHash : CacheArgsAndHash) => {
    return argsAndHash.hash in this.store;
  }

  get = (argsAndHash : CacheArgsAndHash) => {
    const cached = this.store[argsAndHash.hash];

    if (typeof cached === "object" && typeof cached.then === "function") { // Test for a promise
      let handled = false;

      // a race between a first possibly-pending Promise
      // and a certainly-resolved second Promise.  (First
      // position does get precedence)
      const cachedOrRetry = Promise.race([
        // if `cached` is immediately resolved, it will
        // win the race and be what is returned by this
        // invocation of `get()`.  If it is pending but
        // ultimately resolves, it will be ignored here
        // because it will already have been returned by
        // the second position logic.
        cached.catch(() => {
          // If `cached` is *immediately* rejected (it
          // is the cached promise from a previous
          // call which ultimately rejected) then
          // store and return a retry of the same call
          // instead of the cached rejection.  But if
          // `cached` was immediately pending, then
          // `handled` would be set and we should
          // ignore this ultimate late rejection.
          if (!handled) {
            const retry = this.store[argsAndHash.hash] = this.fn(...argsAndHash.args);
            return retry;
          }
        }),
        Promise.resolve().then(() => {
          // If this second position Promise wins the
          // race, it means the first was pending.
          // This handler will be run either way, but
          // the returned value will be ignored if we lost.
          // Assuming we won and the cached promise is
          // pending, mark this particular `get()`
          // invocation handled so that if it
          // ultimately fails we don't retry the
          // request.
          handled = true;
          // return the cached promise which will only
          // be returned by this invocation of `get()`
          // if it is pending.
          return cached;
        })
      ]);
      return cachedOrRetry;
    } else { // `undefined` (not found), or some other non-Promise memoized value
      return cached;
    }
  }

  set = (argsAndHash : CacheArgsAndHash, value : any) => {
    this.store[argsAndHash.hash] = value;
  }
}

// Memoizes a fetcher function (not necessarily production-ready)
export const memoizeAsync = <T extends (...args: any[]) => any>(fn : T) : T => {
  return memoize<T>(
    fn,
    {
      // The typescript types, at least, expect a string.  
      // Hacking it here, because we need the original args.
      // We could deserialize the stringified args, 
      // but this seems a little nicer.
      serializer: args => ({ args, hash: stableStringify(args) } as any), //hack
      cache: {
        create: () => new PromiseAwareMemoizingCache<T>(fn)
      } as any // Something wrong with the typescript types here related to cache.create()
    }
  );
};
Read more comments on GitHub >

github_iconTop Results From Across the Web

Promise - JavaScript - MDN Web Docs
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with...
Read more >
Adding Promise Support to a Node.js Library - Brian Cline
There are a few different ways to convert callbacks to use promises. In the built-in util package, there's a function called promisify() that ......
Read more >
Native Support for Promises in Node.js - Stack Overflow
Node.js added native promise support since version 0.11.13 . nvm install 0.11.12 nvm run 0.11.12 > Promise ReferenceError: Promise is not defined ...
Read more >
Callback and Promise Support in your Node.js Modules
Lift your modules and applications to the next level by supporting promises besides callbacks. Developers out there will thank you for that move ......
Read more >
JavaScript Promises - W3Schools
The Promise object supports two properties: state and result. ... Both are optional, so you can add a callback for success or failure...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found