Home » Javascript » Resolve Javascript Promise outside function scope

Resolve Javascript Promise outside function scope

Posted by: admin November 30, 2017 Leave a comment

Questions:

I have been using ES6 Promise.

Ordinarily, a Promise is constructed and used like this

new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});

But I have been doing something like below to take the resolve outside for the sake of flexibility.

var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) { 
    outsideResolve = resolve; 
    outsideReject = reject; 
});

And later

onClick = function(){
    outsideResolve();
}

This works fine, but is there an easier way to do this? If not, is this a good practice?

Answers:

No, there is no other way to do this – the only thing I can say is that this use case isn’t very common. Like Felix said in the comment – what you do will consistently work.

It’s worth mentioning that the reason the promise constructor behaves this way is throw safety – if an exception you did not anticipate happens while your code is running inside the promise constructor it will turn into a rejection, this form of throw safety – converting thrown errors to rejections is important and helps maintain predictable code.

For this throw safety reason, the promise constructor was chosen over deferreds (which are an alternative promise construction way that do allow what you’re doing) – as for best practices – I’d pass the element and use the promise constructor instead:

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));

For this reason – whenever you can use the promise constructor over exporting the functions – I recommend you do use it. Whenever you can avoid both – avoid both and chain.

Note, that you should never use the promise constructor for things like if(condition), the first example could be written as:

var p = Promise[(someCondition)?"resolve":"reject"]();

Questions:
Answers:

simple:

var promiseResolve, promiseReject;

var promise = new Promise(function(resolve, reject){
  promiseResolve = resolve;
  promiseReject = reject;
});

promiseResolve();

Questions:
Answers:

Bit late to the party here, but another way to do it would be to use a Deferred object. You essentially have the same amount of boilerplate, but it’s handy if you want to pass them around and possibly resolve outside of their definition.

Naive Implementation:

class Deferred {
  constructor() {
    this.promise = new Promise((resolve, reject)=> {
      this.reject = reject
      this.resolve = resolve
    })
  }
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(()=> {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(result => {
  console.log(result) // 42
})

ES5 Version:

function Deferred() {
  var self = this;
  this.promise = new Promise(function(resolve, reject) {
    self.reject = reject
    self.resolve = resolve
  })
}

function asyncAction() {
  var dfd = new Deferred()

  setTimeout(function() {
    dfd.resolve(42)
  }, 500)

  return dfd.promise
}

asyncAction().then(function(result) {
  console.log(result) // 42
})

Questions:
Answers:

Enjoy MYYYYYYYYY PERSONAL solution! I came up with it in 2015 for my framework. I called this type of promises Task

function createPromise(handler){
  var _resolve, _reject;

  var promise = new Promise(function(resolve, reject){
    _resolve = resolve; 
    _reject = reject;

    handler(resolve, reject);
  })

  promise.resolve = _resolve;
  promise.reject = _reject;

  return promise;
}

var promise = createPromise()
promise.then(function(data){ alert(data) })

promise.resolve(200) // resolve from outside

Questions:
Answers:

I came up with something that is basically a minimal Javascript variant of the wait/notify pattern (sans critical section protection, since Javascript does not have that issue).

Fiddle here.

Code:

// create some monitors
var monitors = [
    new Monitor(1),    // rough deadline
    new Monitor(),
    new Monitor(),
    new Monitor()
];

// register event handlers (on "notify event")
for (var i = 0; i < monitors.length; ++i) {
    var monitor = monitors[i];
    monitor.i = i;

    monitor.wait
    .bind(monitor)
    .then(function(result) {
        console.log('Success ' + this.i + ': ' + result);
    })
    .catch(function(err) {
        console.error('Failure ' + this.i + ': ' + err);
    });
}

// notify like a bawss
Promise
.delay(100)
.then(function() {
    monitors[0].notifyResolve('hi! :)');
    monitors[1].notifyReject('hi! :(');
    monitors[2].notifyResolve('hi! :)');

    // forgot about the fourth monitor: it'll timeout
});

// Monitor class
function Monitor(timeoutMillis) {
    timeoutMillis = timeoutMillis || 1000;

    var resolve, reject;
    var isResolved = false, err, result;

    var promise = new Promise(function(_resolve, _reject) {
        if (isResolved) {
            if (err) {
                _reject(err)
            }
            else {
                _resolve(result);
            }
        }
        else {
            resolve = _resolve;
            reject = _reject;
        }
    });

    // make sure, promise will be fulfilled
    if (timeoutMillis >= 0) {    // negative value means: no timeout
        setTimeout(function() {
            if (!isResolved) {
                this.notifyReject('timeout');
            }
        }.bind(this), timeoutMillis);
    }

    this.wait = promise;
    this.notifyResolve = function(_result) {
        if (isResolved) return;
        isResolved = true;

        if (resolve) {
            resolve(_result);
        }
        else {
            // remember result until Promise ctor callback is called
            result = _result;
        }
    };
    this.notifyReject = function(_err) {
        if (isResolved) return;
        isResolved = true;

        if (reject) {
            reject(_err);
        }
        else {
            // remember result until Promise ctor callback is called
            err = _err;
        }
    };
};

Result:

Failure 0: timeout

Success 2: hi! 🙂

Failure 1: hi! 🙁

Failure 3: timeout

Questions:
Answers:

Our solution was to use closures to store the resolve/reject functions and additionally attach a function to extend the promise itself.

Here is the pattern:

function getPromise() {

    var _resolve, _reject;

    var promise = new Promise((resolve, reject) => {
        _reject = reject;
        _resolve = resolve;
    });

    promise.resolve_ex = (value) => {
       _resolve(value);
    };

    promise.reject_ex = (value) => {
       _reject(value);
    };

    return promise;
}

And using it:

var promise = getPromise();

promise.then(value => {
    console.info('The promise has been fulfilled: ' + value);
});

promise.resolve_ex('hello');  
// or the reject version 
//promise.reject_ex('goodbye');

Questions:
Answers:

For your use case, there is definitely a better way.

const promise = new Promise(function(resolve, reject){
  if (someCondition){
    resolve()
  } else {
    reject()
  } 
})

is equivalent to:

const promise = Promise[someCondition ? 'resolve' : 'reject']()

and then later:

onClick = function(){
  promise
    .then(() => /** handle resolve */)
    .catch(err => /** handle reject */)
}

Promises are useful for async work and this example has no async work making it an anti-pattern. You are better to use promises with implementations such as fetch that natively return promises and use the constructor style only when working with an incompatible async interface (node callbacks).

The cleanest code is no code at all.

Questions:
Answers:

I liked @JonJaques answer but I wanted to take it a step further.

If you bind then and catch then the Deferred object, then it fully implements the Promise API and you can treat it as promise and await it and such.

class DeferredPromise {
  constructor() {
    this._promise = new Promise((resolve, reject) => {
      // assign the resolve and reject functions to `this`
      // making them usable on the class instance
      this.resolve = resolve;
      this.reject = reject;
    });
    // bind `then` and `catch` to implement the same interface as Promise
    this.then = this._promise.then.bind(this._promise);
    this.catch = this._promise.catch.bind(this._promise);
    this[Symbol.toStringTag] = 'Promise';
  }
}

const deferred = new DeferredPromise();
console.log('waiting 2 seconds...');
setTimeout(() => {
  deferred.resolve('whoa!');
}, 2000);

async function someAsyncFunction() {
  const value = await deferred;
  console.log(value);
}

someAsyncFunction();