Resolve Javascript Promise outside function scope

2019-01-08 03:57发布

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?

9条回答
叼着烟拽天下
2楼-- · 2019-01-08 04:08

A solution I came up with 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
查看更多
该账号已被封号
3楼-- · 2019-01-08 04:08

I wrote a small lib for this. https://www.npmjs.com/package/@inf3rno/promise.exposed

I used the factory method approach others wrote, but I overrode the then, catch, finally methods too, so you can resolve the original promise by those as well.

Resolving Promise without executor from outside:

const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");

Racing with the executor's setTimeout from outside:

const promise = Promise.exposed(function (resolve, reject){
    setTimeout(function (){
        resolve("I almost fell asleep.")
    }, 100000);
}).then(console.log);

setTimeout(function (){
    promise.resolve("I don't want to wait that much.");
}, 100);

There is a no-conflict mode if you don't want to pollute the global namespace:

const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");
查看更多
Ridiculous、
4楼-- · 2019-01-08 04:10

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();

查看更多
不美不萌又怎样
5楼-- · 2019-01-08 04:17

I'm using a helper function to create what I call a "flat promise" -

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
      resolve = res;
      reject = rej;
    });

    return { promise, resolve, reject };
}

And I'm using it like so -

function doSomethingAsync() {

    // Get your promise and callbacks
    const { resolve, reject, promise } = flatPromise();

    // Do something amazing...
    setTimeout(() => {
        resolve('done!');
    }, 500);

    // Pass your promise to the world
    return promise;

}

See full working example -

function flatPromise() {

    let resolve, reject;

    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });

    return { promise, resolve, reject };
}

function doSomethingAsync() {
    
    // Get your promise and callbacks
    const { resolve, reject, promise } = flatPromise();

    // Do something amazing...
    setTimeout(() => {
        resolve('done!');
    }, 500);

    // Pass your promise to the world
    return promise;
}

(async function run() {

    const result = await doSomethingAsync()
        .catch(err => console.error('rejected with', err));
    console.log(result);

})();

Edit: I have created an NPM package called flat-promise and the code is also available on GitHub.

查看更多
一纸荒年 Trace。
6楼-- · 2019-01-08 04:18

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"]();
查看更多
Viruses.
7楼-- · 2019-01-08 04:18

A helper method would alleviate this extra overhead, and give you the same jQuery feel.

function Deferred() {
    let resolve;
    let reject;
    const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
    });
    return { promise, resolve, reject };
}

Usage would be

const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
    confirm: resolve,
    cancel: reject
});
return promise;

Which is similar to jQuery

const dfd = $.Deferred();
displayConfirmationDialog({
    confirm: dfd.resolve,
    cancel: dfd.reject
});
return dfd.promise();

Although, in a use case this simple, native syntax is fine

return new Promise((resolve, reject) => {
    displayConfirmationDialog({
        confirm: resolve,
        cancel: reject
    });
});
查看更多
登录 后发表回答