在功能范围之外解决Javascript Promise

Mor*_*rio 248 javascript promise es6-promise

我一直在使用ES6 Promise.

通常,Promise是这样构造和使用的

new Promise(function(resolve, reject){
    if (someCondition){
        resolve();
    } else {
        reject();
    } 
});
Run Code Online (Sandbox Code Playgroud)

但我一直在做类似下面的事情,为了灵活性而采取外面的决心.

var outsideResolve;
var outsideReject;
new Promise(function(resolve, reject) { 
    outsideResolve = resolve; 
    outsideReject = reject; 
});
Run Code Online (Sandbox Code Playgroud)

然后

onClick = function(){
    outsideResolve();
}
Run Code Online (Sandbox Code Playgroud)

这很好,但是有更简单的方法吗?如果没有,这是一个好习惯吗?

car*_*ter 114

简单:

var promiseResolve, promiseReject;

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

promiseResolve();
Run Code Online (Sandbox Code Playgroud)

  • 问题中已经提到了这个确切的结构。你甚至读过吗? (12认同)
  • 我甚至不确定这是一个糟糕的设计.在promise之外抛出的错误不应该被包含在promise中.如果设计师实际上_展示了要捕获的错误,那么它可能是误解或错误理解的一个例子. (7认同)
  • @JonJaques我不确定你说的是不是真的.调用`promiseResolve()`的代码不会抛出异常.你可以在构造函数上定义一个`.catch`,无论代码调用它,都会调用构造函数的`.catch`.以下是jsbin演示如何工作:https://jsbin.com/yicerewivo/edit?js,console (4认同)
  • 是的,它被捕获是因为你在它周围包裹了另一个承诺构造函数 - 这正是我想要表达的观点。但是,假设您有一些其他代码试图在构造函数(又名延迟对象)之外调用resolve()...它可能会抛出异常并且不会被捕获https://jsbin.com/cokiqiwapo/1/edit ?js,控制台 (3认同)
  • @ruX,正如公认的答案所提到的那样-它是故意设计的。关键是,如果引发异常,则将由promise构造函数捕获。这个答案(以及我的答案)都有可能会抛出异常,无论代码调用“ promiseResolve()”如何。许诺的语义是*总是*返回一个值。另外,这在功能上与OP的帖子相同,我不知道这是以可重用的方式解决的问题。 (2认同)

Jon*_*ues 85

这里的派对迟到了,但另一种方法是使用Deferred对象.你基本上有相同数量的样板,但是如果你想传递它们并且可能在它们的定义之外解析它会很方便.

天真的实施:

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
})
Run Code Online (Sandbox Code Playgroud)

ES5版本:

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
})
Run Code Online (Sandbox Code Playgroud)

  • 使用延迟是执行此操作的常用方法,我不知道为什么这不更高 (2认同)
  • 很好的答案!正在寻找 jQuery 提供的延迟功能。 (2认同)
  • 不推荐使用“延期”吗? (2认同)

Ben*_*aum 83

不,没有其他方法可以做到这一点 - 我唯一可以说的是这个用例并不常见.就像菲利克斯在评论中所说的那样 - 你所做的将会持续发挥作用.

值得一提的是,promise构造函数以这种方式运行的原因是抛出安全性 - 如果您在代码在promise构造函数内运行时没有预料到会发生异常,它将变成拒绝,这种抛出安全形式 - 将抛出的错误转换为拒绝很重要,有助于维护可预测的代码.

对于这个抛出安全原因,promise构造函数被选择为延迟(这是一种替代的承诺构造方式,允许你正在做的事情) - 至于最佳实践 - 我将传递元素并使用promise构造函数:

var p = new Promise(function(resolve, reject){
    this.onclick = resolve;
}.bind(this));
Run Code Online (Sandbox Code Playgroud)

出于这个原因 - 每当你可以使用promise构造函数而不是导出函数时 - 我建议你使用它.每当你可以避免两者 - 避免两者和链.

注意,你应该永远不要使用promise构造函数if(condition),第一个例子可以写成:

var p = Promise[(someCondition)?"resolve":"reject"]();
Run Code Online (Sandbox Code Playgroud)

  • 不常见?我最终几乎每个项目都需要它. (69认同)
  • 如果你可以'var p = new Promise(); 那就太方便了 p.resolve()` (6认同)
  • 嗨,本杰明!如果我们不知道什么时候兑现承诺,目前有没有更好的方法来获得美味的承诺糖?像某种异步[等待/通知模式](http://www.programcreek.com/2009/02/notify-and-wait-example/)?例如,“存储”,然后调用 `Promise` 链?例如,在我的特定情况下,我在服务器上,等待特定的客户端回复(SYN-ACK 有点握手以确保客户端成功更新状态)。 (2认同)
  • 使用提取API怎么办? (2认同)
  • 至于用例,请考虑您需要在触发事件并发生其他事情后做某事。您想将事件转换为承诺并将其与另一个承诺联合起来。对我来说似乎是一个普遍的问题。 (2认同)
  • @BenjaminGruenbaum - 另一个用例是如果您正在与网络工作者进行通信。如果您期望通过网络工作者消息到达多条信息(以未知的顺序),那么最好为每条信息做出承诺 p[i],这样该信息的消费者就可以等待该承诺或注册一个通过 p[i].then(callme) 回调。这个承诺需要通过worker.onmessage来解决,而不是通过承诺创建时提供的代码来解决。(或者被worker.onerror中的代码拒绝。)基本上,每当异步进程触发多个无序回调时,您都会想要OP正在谈论的内容。 (2认同)

Max*_*mus 18

我在2015年为我的框架提出的解决方案.我称这种承诺为任务

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
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,这工作。但是什么是处理程序?我必须将其删除才能正常工作。 (3认同)

小智 16

以防万一有人来寻找简化此任务的 util 的打字稿版本:

export const deferred = <T>() => {
  let resolve!: (value: T | PromiseLike<T>) => void;
  let reject!: (reason?: any) => void;
  const promise = new Promise<T>((res, rej) => {
    resolve = res;
    reject = rej;
  });

  return {
    resolve,
    reject,
    promise,
  };
};
Run Code Online (Sandbox Code Playgroud)

这可以使用,例如。喜欢:

const {promise, resolve} = deferred<string>();

promise.then((value) => console.log(value)); // nothing

resolve('foo'); // console.log: foo

Run Code Online (Sandbox Code Playgroud)


Ali*_*Ali 15

接受的答案是错误的。使用范围和引用非常容易,尽管它可能会让 Promise纯粹主义者生气:

const createPromise = () => {
    let resolver;
    return [
        new Promise((resolve, reject) => {
            resolver = resolve;
        }),
        resolver,
    ];
};

const [ promise, resolver ] = createPromise();
promise.then(value => console.log(value));
setTimeout(() => resolver('foo'), 1000);

Run Code Online (Sandbox Code Playgroud)

我们本质上是在创建 Promise 时获取对 resolve 函数的引用,然后我们返回它以便可以在外部设置它。

一秒钟后,控制台将输出:

> foo
Run Code Online (Sandbox Code Playgroud)


Ric*_*ler 14

我喜欢@JonJaques的回答,但我想更进一步.

如果绑定thencatch随后的Deferred对象,那么它完全实现了PromiseAPI,你可以把它当作承诺,await它与这样的.

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();
Run Code Online (Sandbox Code Playgroud)


Cor*_*son 10

辅助方法可以减轻这些额外的开销,并为您提供相同的jQuery感觉.

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

用法是

const { promise, resolve, reject } = Deferred();
displayConfirmationDialog({
    confirm: resolve,
    cancel: reject
});
return promise;
Run Code Online (Sandbox Code Playgroud)

这与jQuery类似

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

虽然,在一个用例中,这种简单的原生语法很好

return new Promise((resolve, reject) => {
    displayConfirmationDialog({
        confirm: resolve,
        cancel: reject
    });
});
Run Code Online (Sandbox Code Playgroud)


jam*_*ess 8

这里的许多答案都类似于在最后一个例子这篇文章。我正在缓存多个 Promise,并且可以将resolve()reject()函数分配给任何变量或属性。因此,我可以使这段代码稍微紧凑一些:

function defer(obj) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
}
Run Code Online (Sandbox Code Playgroud)

以下是使用此版本defer()FontFace负载 Promise 与另一个异步进程组合的简化示例:

function onDOMContentLoaded(evt) {
    let all = []; // array of Promises
    glob = {};    // global object used elsewhere
    defer(glob);
    all.push(glob.promise);
    // launch async process with callback = resolveGlob()

    const myFont = new FontFace("myFont", "url(myFont.woff2)");
    document.fonts.add(myFont);
    myFont.load();
    all.push[myFont];
    Promise.all(all).then(() => { runIt(); }, (v) => { alert(v); });
}
//...
function resolveGlob() {
    glob.resolve();
}
function runIt() {} // runs after all promises resolved 
Run Code Online (Sandbox Code Playgroud)

更新:如果您想封装对象,有两种选择:

function defer(obj = {}) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
    return obj;
}
let deferred = defer();
Run Code Online (Sandbox Code Playgroud)

class Deferred {
    constructor() {
        this.promise = new Promise((resolve, reject) => {
            this.resolve = resolve;
            this.reject  = reject;
        });
    }
}
let deferred = new Deferred();
Run Code Online (Sandbox Code Playgroud)


小智 8

在某些情况下,我发现自己也缺少延迟模式。你总是可以在 ES6 Promise 之上创建一个:

export default class Deferred<T> {
    private _resolve: (value: T) => void = () => {};
    private _reject: (value: T) => void = () => {};

    private _promise: Promise<T> = new Promise<T>((resolve, reject) => {
        this._reject = reject;
        this._resolve = resolve;
    })

    public get promise(): Promise<T> {
        return this._promise;
    }

    public resolve(value: T) {
        this._resolve(value);
    }

    public reject(value: T) {
        this._reject(value);
    }
}
Run Code Online (Sandbox Code Playgroud)


Ari*_*rik 7

我正在使用辅助函数来创建我称之为"扁平承诺"的东西 -

function flatPromise() {

    let resolve, reject;

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

    return { promise, resolve, reject };
}
Run Code Online (Sandbox Code Playgroud)

而我正在使用它 -

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;

}
Run Code Online (Sandbox Code Playgroud)

查看完整的工作示例 -

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

})();
Run Code Online (Sandbox Code Playgroud)

编辑:我创建了一个名为flat-promise的NPM包,代码也可以在GitHub上找到.


Hin*_*ich 6

您可以将Promise包装在一个类中。

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

        this.promise.resolve = this.resolve;
        this.promise.reject = this.reject;

        return this.promise;
    }
    promise;
    resolve;
    reject;
}

// How to use.
const promise = new Deferred((resolve, reject) => {
  // Use like normal Promise.
});

promise.resolve(); // Resolve from any context.
Run Code Online (Sandbox Code Playgroud)


Ste*_*gin 5

我们的解决方案是使用闭包来存储解析/拒绝函数,并另外附加一个函数来扩展承诺本身。

这是模式:

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;
}
Run Code Online (Sandbox Code Playgroud)

并使用它:

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');
Run Code Online (Sandbox Code Playgroud)

  • 太棒了......我刚刚学习 Promise,但一直对你似乎无法在“其他地方”解决它们这一事实感到困惑。使用闭包来隐藏实现细节是一个好主意......但事实上我不确定这就是你所做的:而不是使用“伪”私有变量,我很确定有一种方法可以*完全*隐藏应该无法访问的变量...这就是闭包的真正含义... (3认同)
  • 问题是如何在范围之外解决它。这是一个有效的解决方案,在我们的生产中,我们实际上有必要的理由这样做。我不明白为什么解决上述问题值得投反对票。 (2认同)