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.

A standard promise with observable state and value

See original GitHub issue

I’ve implemented a simple Promise that has two observable properties: value and state. It is exactly like fromPromise but it is actually a Promise. As an example I have converted axios.get result and:

  const myPromise = toObservablePromise(axios.get('/api/get'));
  
  // It is possible to write chained then functions
  const myNewPromise = myPromise
    .then(response => response.data)
    .then(data => console.log(data))
    // Also you can write a catch handler
    .catch(err => console.error(err));

  // And in every step it is possible to observe the state
  when(
    () => myNewPromise.state === 'fulfilled',
    () => {
      console.log(myNewPromise.value); // data
  });

  when(
    () => myPromise.state === 'fulfilled',
    () => {
      console.log(myPromise.value); // response
  });

  // It is possible to write async tests easily
  it('should run this async test without done function', () => {
    return myNewPromise
       .then(data => {
         expect(data).toEqual(...);
       });
  });

  // Also it is possible to watch the whole process using autorun
  autorun(() => {
    console.log('Promise state=', myNewPromise.state, ', value=', myNewPromise.value);
  });
  // Promise state=pending, value=undefined
  // Promise state=fulfilled, value=<data>

  // also you can use Promise.all
  Promise.all(myPromise, new ObservablePromise(Promise.resolve('value')), aNativePromise);

Having this gives the feeling that you are working with a native promise 😆 .

Here is a simple implementation for ObservablePromise and toObservablePromise (Promise implementation pasted from here):

import { observable } from 'mobx';
/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
  const t = typeof value;

  if (value && (t === 'object' || t === 'function')) {
    const then = value.then;

    if (typeof then === 'function') {
      return then;
    }
  }

  return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
  let done = false;

  try {
    fn((value) => {
      if (done) return;
      done = true;
      onFulfilled(value);
    }, (reason) => {
      if (done) return;
      done = true;
      onRejected(reason);
    });
  } catch (ex) {
    if (done) return;
    done = true;
    onRejected(ex);
  }
}

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function ObservablePromise(fn) {
  // store state which can be PENDING, FULFILLED or REJECTED
  const state = observable.box(PENDING);

  // store value once FULFILLED or REJECTED
  const value = observable.box(null);

  // store sucess & failure handlers
  let handlers = [];

  function handle(handler) {
    if (state.get() === PENDING) {
      handlers.push(handler);
    } else {
      if (state.get() === FULFILLED &&
        typeof handler.onFulfilled === 'function') {
        handler.onFulfilled(value.get());
      }
      if (state.get() === REJECTED &&
        typeof handler.onRejected === 'function') {
        handler.onRejected(value.get());
      }
    }
  }

  function fulfill(result) {
    value.set(result);
    state.set(FULFILLED);
    handlers.forEach(handle);
    handlers = null;
  }

  function reject(error) {
    value.set(error);
    state.set(REJECTED);
    handlers.forEach(handle);
    handlers = null;
  }

  function resolve(result) {
    try {
      const then = getThen(result);

      if (then) {
        doResolve(then.bind(result), resolve, reject);

        return;
      }
      fulfill(result);
    } catch (e) {
      reject(e);
    }
  }

  this.done = (onFulfilled, onRejected) => {
    // ensure we are always asynchronous
    setTimeout(() => {
      handle({
        onFulfilled,
        onRejected
      });
    }, 0);
  };

  this.then = (onFulfilled, onRejected) =>
    new ObservablePromise((resolveFn, rejectFn) => this.done((result) => {
      if (typeof onFulfilled === 'function') {
        try {
          return resolveFn(onFulfilled(result));
        } catch (ex) {
          return rejectFn(ex);
        }
      } else {
        return resolveFn(result);
      }
    }, (error) => {
      if (typeof onRejected === 'function') {
        try {
          return resolveFn(onRejected(error));
        } catch (ex) {
          return rejectFn(ex);
        }
      } else {
        return rejectFn(error);
      }
    }));

  this.catch = (onRejected) => this.then(null, onRejected);

  Object.defineProperty(this, 'value', {
    get() {
      return value.get();
    }
  });
  Object.defineProperty(this, 'state', {
    get() {
      return state.get();
    }
  });

  doResolve(fn, resolve, reject);
}

export default function toObservablePromise(promise) {
  return new ObservablePromise((resolve, reject) => {
    promise.then(resolve, reject);
  });
}

export { ObservablePromise };

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
alisabzevaricommented, Mar 8, 2018

If we could chain ObservablePromise this would be the implementation of Loading component (without throwing a promise):

const Loading = observer(({ children }) => {
  {children.case({
    fulfilled: value => value,
    pending: () => <div>Loading...</div>,
    rejected: () => <div>Something went wrong!</div>
  })}
});

My argument is that if we could hack around promise, we could do some cool things like this example. Currently, it is not possible because then and catch methods of ObservablePromise does not return an ObservablePromise.

Regarding the first example, Mobx has been already hacking/wrapping native apis to make them still feel as native apis with additional capabilities as being observed. The same can be applied here, for the promise, to leave the touch and feel of the native api with additional capabilities.

1reaction
mweststratecommented, Jul 25, 2017

Thanks for the idea @alisabzevari. fromPromise now returns a promise in 3.0.0, which is enhanced with the typical fromPromise capabilities

Read more comments on GitHub >

github_iconTop Results From Across the Web

What is the difference between Promises and Observables?
Promises and Observables both handle the asynchronous call only. A promise does not emit a value at all - a promise is a...
Read more >
Promise - JavaScript - MDN Web Docs
A "fulfilled" state indicates a successful completion of the promise, while a "rejected" state indicates a lack of success. The return value of ......
Read more >
JavaScript Promises vs. RxJS Observables - Auth0
A JavaScript Promise is an object that produces a single value, asynchronously. Data goes in, and a single value is emitted and used....
Read more >
RxJS Tips — Promises Into Observables - Medium
Using the merge() RxJS operator we are able to create an Observable that uses the values from all 3 sources and will fire...
Read more >
Observable methods to Promises - Nuclia
infer V is a type variable that will be replaced by the type of the value returned by the observable returned by the...
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