在Promise构造函数范围之外解决Javascript Promise

513

我一直在使用ES6 Promise。

通常,Promise的构造和使用方式如下所示:

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

但是我一直在做以下类似的事情,为了灵活性而将解决方案转移到外部。

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

之后

onClick = function(){
    outsideResolve();
}

这个方法可以正常工作,但有没有更简单的方法?如果没有,这是一个好的做法吗?


2
我认为没有其他方法。我相信规定Promise传递的回调必须同步执行,以允许“导出”这两个函数。 - Felix Kling
1
这对我来说完全像你写的那样有效。就我而言,这是“规范”的方式。 - Gilad Barner
47
我认为未来应该有一种正式的方式来实现这一点。在我看来,这个功能非常强大,因为你可以等待来自其他上下文的值。 - Jose
1
我认为Promise API“建议”我们始终将它们用作返回值,而不是可以访问或调用的对象。换句话说,强制我们将它们视为返回值,而不是我们可以访问或调用的对象、我们可以使用变量引用或作为参数传递的函数等等。如果您开始像使用其他对象一样使用promise,那么您最终可能需要从外部解决它,就像在您的问题中一样... 话虽如此,我也认为应该有一种正式的方法来做到这一点... 而Deferred对我来说只是一个解决方法。 - cancerbero
@Jose 对我来说,有几次它非常有用,当我需要一个 Promise 在另一个 Promise 之后立即解决时,在 Promise.all 或 Promise.race 不起作用的情况下。 - JulianSoto
显示剩余2条评论
27个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
11

在某些情况下,我发现自己也会想念延迟模式。你可以在 ES6 Promise 的基础上创建一个 Deferred:

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

1
我喜欢这个。我只会将签名从reject更改为reject(reason:any)。 - Bruno Marotta

9

这里的许多答案与这篇文章中的最后一个示例类似。 我正在缓存多个 Promises,resolve()reject() 函数可以分配给任何变量或属性。结果,我能够使这段代码稍微更加简洁:

function defer(obj) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
}
以下是关于如何使用此版本的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 
更新:如果您想封装对象,有两种替代方法:
function defer(obj = {}) {
    obj.promise = new Promise((resolve, reject) => {
        obj.resolve = resolve;
        obj.reject  = reject;
    });
    return obj;
}
let deferred = defer();

并且

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

如果您在异步函数中使用这些示例,当您想要使用已解析的 promise 的值时,您需要引用 promise 属性:const result = await deferred.promise; - b00t

5
我们的解决方案是使用闭包来存储resolve/reject函数,并附加一个函数来扩展promise本身。 以下是模式:
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;
}

并使用它:

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

3
很好...我正在学习Promise,但一直被事实困惑,即似乎无法在“其他地方”解决它们。使用闭包隐藏实现细节是一个好主意...但事实上,我不确定这是否是您所做的:与其拥有“伪”私有变量,我相当确定有一种方法可以完全隐藏应该是不可访问的变量...这才是闭包的真正含义... - mike rodent
闭包是一段代码块,可以被引用(并传递),同时可以访问封闭作用域中的变量。其中 var _resolve, _reject; 就是封闭作用域。 - Steven Spungin
没错,说得没毛病。实际上,在我看来,我的答案有些过于复杂了,而且你的答案可以被简化:你只需要写成 promise.resolve_ex = _resolve; promise.reject_ex = _reject; ... 一样能工作良好。 - mike rodent
“将一个函数附加到扩展 Promise 本身。” - 不要这样做。Promise 是结果值,它们不应该提供解决它们的能力。您不希望传递那些扩展的 Promise。 - Bergi
2
问题是如何在范围之外解决它。这里有一个可行的解决方案,在我们的生产环境中,我们实际上有必要这样做。我不明白为什么解决所述问题应该被投票否决。 - Steven Spungin

3
感谢所有在本帖中发表过言论的人,我创建了一个模块,其中包括早先描述的Defer()对象以及其他几个建立在其之上的对象。它们都利用了Promises以及优雅的Promise回调语法来实现程序内的通信/事件处理。
  • Defer:可以在其体外解决或失败的Promise。
  • Delay:在给定时间后自动解决的Promise。
  • TimeOut:在给定时间后自动失败的Promise。
  • Cycle:可重新触发的Promise,用于使用Promise语法管理事件。
  • Queue:基于Promise链接的执行队列。

rp = require("openpromise")

https://github.com/CABrouwers/openpromise https://www.npmjs.com/package/openpromise


3

是的,你可以。通过使用浏览器环境下的CustomEventAPI,并在node.js环境中使用事件发射器项目。由于问题中的片段是针对浏览器环境的,因此以下是相同功能的工作示例。

function myPromiseReturningFunction(){
  return new Promise(resolve => {
    window.addEventListener("myCustomEvent", (event) => {
       resolve(event.detail);
    }) 
  })
}


myPromiseReturningFunction().then(result => {
   alert(result)
})

document.getElementById("p").addEventListener("click", () => {
   window.dispatchEvent(new CustomEvent("myCustomEvent", {detail : "It works!"}))
})
<p id="p"> Click me </p>

我希望这个答案对您有所帮助!

3

在TypeScript中的类版本:

export class Deferred<T> {
    public readonly promise: Promise<T>
    private resolveFn!: (value: T | PromiseLike<T>) => void
    private rejectFn!: (reason?: any) => void

    public constructor() {
        this.promise = new Promise<T>((resolve, reject) => {
            this.resolveFn = resolve
            this.rejectFn = reject
        })
    }

    public reject(reason?: any): void {
        this.rejectFn(reason)
    }

    public resolve(param: T): void {
        this.resolveFn(param)
    }
}

1
我制作了一个名为manual-promise的库,可作为Promise的替代品。这里的其他答案都不能作为Promise的替代品,因为它们使用代理或包装器。 使用以下命令安装:yarn add manual-promisenpn install manual-promise

import { ManualPromise } from "manual-promise";

const prom = new ManualPromise();

prom.resolve(2);

// actions can still be run inside the promise
const prom2 = new ManualPromise((resolve, reject) => {
    // ... code
});


new ManualPromise() instanceof Promise === true

https://github.com/zpxp/manual-promise#readme


1

如果(像我一样)你不喜欢增强本地实例,也不喜欢笨重的".promise"属性...但是喜欢代理和混淆类,那么这个就是为你准备的:

class GroovyPromise {
  constructor() {
    return new Proxy(new Promise((resolve, reject) => {
      this.resolve = resolve;
      this.reject = reject;
    }), {
      get: (target, prop) =>
        this[prop] || target[prop].bind(target),
    });
  }
}

使用方法如下:

const groovypromise = new GroovyPromise();
setTimeout(() => groovypromise.resolve('groovy'), 1000);
console.log(await groovypromise);

当然,你也可以将类重命名为像"Deferred"这样的无聊名称。


1

这是另一种解决从外部解析 Promise 的方法。

 class Lock {
        #lock;  // Promise to be resolved (on  release)
        release;  // Release lock
        id;  // Id of lock
        constructor(id) {
            this.id = id
            this.#lock = new Promise((resolve) => {
                this.release = () => {
                    if (resolve) {
                        resolve()
                    } else {
                        Promise.resolve()
                    }
                }
            })
        }
        get() { return this.#lock }
    }

使用方法

let lock = new Lock(... some id ...);
...
lock.get().then(()=>{console.log('resolved/released')})
lock.release()  // Excpected 'resolved/released'

1
我为此编写了一个小型库。https://www.npmjs.com/package/@inf3rno/promise.exposed 我采用了其他人编写的工厂方法,但我也重写了thencatchfinally方法,因此您也可以通过这些方法解决原始的Promise。 在外部解决Promise而不需要执行器:
const promise = Promise.exposed().then(console.log);
promise.resolve("This should show up in the console.");

从外部使用executor的setTimeout进行竞速:

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);
如果您不想污染全局命名空间,可以使用无冲突模式:
const createExposedPromise = require("@inf3rno/promise.exposed/noConflict");
const promise = createExposedPromise().then(console.log);
promise.resolve("This should show up in the console.");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,