6

I'm trying to extend the javascript promise with a new method. This new method in this case is called foo, which really does something like this:

Promise.foo = function(arg) {
  return this.then( function(result) {
    return result.foo(arg);
  });
};

So in short, the foo() function is a shortcut for waiting for a promise to resolve and then calling foo() on the result.

The nature of this function is that it can be chained, just like then() can.

myPromise.foo(a).foo(b).foo(c);

I feel like this should be possible, but I'm just not sure what the right path is.

This is what I've tried:

var FooPromise = function() {
   Promise.apply(this, arguments);
}

FooPromise.prototype = Object.create(Promise.prototype);
FooPromise.foo = function(arg) {
  return this.then( function(result) {
    return result.foo(arg);
  });
};

To test it out:

var test = new FooPromise(function(res, rej) {
   res('bla');
});

In firefox this gives me:

TypeError: calling a builtin Promise constructor without new is forbidden

In Node:

TypeError: #<Promise> is not a promise

Is this just a limitation of javascript, or is there a way around this?

Evert
  • 75,014
  • 17
  • 95
  • 156
  • 2
    perhaps you should use `FooPromise.prototype.foo` - because you'll be working with instances of FooPromise (why not just add it to Promise.prototype.foo in the first place and not use FooPromise at all) -as for your attempt at "inheriting" from Promise - it looks 3 kinds of wrong to begin with – Jaromanda X Apr 18 '17 at 05:34
  • See [Delay chained promise](http://stackoverflow.com/questions/38734106/delay-chained-promise/38734306#38734306) – guest271314 Apr 18 '17 at 05:35
  • What is `result.foo`? – guest271314 Apr 18 '17 at 05:42
  • 1
    Apparently you can **not** extend `Promise` without using the ES6 `class FooPromise extends Promise` syntax shown by @anete.anetes , because Promise checks [`new.target`](http://stackoverflow.com/q/32450516/5217142) to see if it is called as a constructor - and is required in the ECMA specs to throw an error if not. – traktor Apr 18 '17 at 07:53
  • @JaromandaX because I just want to 'subclass' it within the context of my library, I don't want to do a global monkey-patch because I think generally that's a bad practice. – Evert Apr 18 '17 at 14:20
  • Fair enough. I don't have a problem with extending built ins myself – Jaromanda X Apr 18 '17 at 14:28
  • @JaromandaX that might be good enough for your own application, but you wouldn't want your library dependencies to do this. Generally I think it's a good thing for libraries to avoid global modifications. – Evert Apr 18 '17 at 14:56
  • @Evert - must be nice not to have to polyfill for Internet Exploder – Jaromanda X Apr 18 '17 at 22:42
  • @JaromandaX Overriding globals to polyfill internet standards in browsers that don't support them is a great exception to the 'don't override globals' rule. My use-case kinda falls outside of that category though. – Evert Apr 19 '17 at 00:21

2 Answers2

9

ES6 way:

class FooPromise extends Promise {
    constructor(executor) {
        super(executor);
    }
}

var fooPromise = new FooPromise((resolve,reject)=>{
   resolve(null);
});
  • __TypeError__: The executor of a promise must be a function. – traktor Apr 18 '17 at 06:07
  • of course! You need implement basic constructor args) for example: `var fooPromise = new FooPromise((resolve,reject)=>{resolve(1);});` –  Apr 18 '17 at 06:13
  • Thank you, this is great! I take it this is the only way to do this? Bit worried about using ES6 syntax in browsers still, but perhaps I need to stop worrying =) – Evert Apr 18 '17 at 14:24
  • my choise is [tag:typescript] –  Apr 18 '17 at 14:43
  • @JaromandaX I'd like to avoid transpiling. My goal is build a lightweight library with a low dependency count. – Evert Apr 18 '17 at 14:55
  • `I'd like to avoid transpiling` and yet you chose an ES6 answer as the best!? My suggestion for using babel was to better understand how to "properly" extend Promise in ES5 – Jaromanda X Apr 18 '17 at 22:41
  • @JaromandaX do you have an ES5 answer? Afaik Babel actually also has a bug related to this. The other javascript answer is to build your own thenable or use something like bluebird. I would LOVE an ES5 solution though that doesn't involve writing a promise from scratch. If you got one I'd totally prefer that answer. – Evert Apr 19 '17 at 00:27
  • 1
    @user4074041 what if I need to resolve or reject my extended promise inside the subclass constructor? In the provided solution resolve / reject functions are not accessible. – humkins Jan 01 '18 at 10:23
3

After more research, I landed on the following solution. There's no need to extend the built-in Promise. All you really need is to make sure your object implements then correctly (aka Promise/A+ / thenable).

function FooPromise(executor) {

  this.innerPromise = new Promise(executor);

}

FooPromise.prototype = {

 then: function(onFulfilled, onRejected) {

    return new FooPromise(function(res, rej) {

       this.innerPromise.then(res, rej);

    });

  },

  foo: function() {

    return this.then(function(val) {

      return val.foo();

    });

  }

}

This works fine in ES5 environments, works perfectly with other promises and even async/await (where available).

I successfully implemented this pattern this open source library.

Evert
  • 75,014
  • 17
  • 95
  • 156
  • 1
    I like that at 4 years of JS dev Im second guessing if this is yet another poorly documented way of defining a object or just bad syntax. I am 99% sure that this is bad syntax though. – gbtimmon Nov 06 '18 at 15:35
  • @gbtimmon: **edit** I see the problem. Disregard last comment – Evert Nov 06 '18 at 17:07
  • Dont't forget to also implement catch & finally, which Promise consumers will also expect to exist. – rektide Jan 27 '20 at 20:59