在Promise构造函数作用域之外解决Javascript Promise

我一直在使用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();
}

这很好,但是否有更简单的方法来做到这一点?如果不是,这是一个好的实践吗?

148896 次浏览

不,没有其他方法可以做到这一点——我唯一能说的是这个用例不是很常见。就像Felix在评论中说的那样——你所做的将始终有效。

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

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

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

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

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

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

有点晚了,但另一种方法是使用递延对象。你基本上有相同数量的样板文件,但是如果你想要传递它们并且可能在它们的定义之外解析,这很方便。

天真的实现:

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版本:

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

简单:

var promiseResolve, promiseReject;


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


promiseResolve();

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

模式如下:

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

我在2015年为我的框架提出了一个解决方案。我把这种类型的承诺称为任务

function createPromise(handler){
var resolve, reject;


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

promise.resolve = resolve;
promise.reject = reject;
return promise;
}




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


// resolve from outside
promise.resolve(200)

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

如果你绑定thencatch,那么Deferred对象,那么它完全实现了Promise API,你可以把它视为承诺和await它等等。

⚠️编者注:我不再推荐这种模式,因为在撰写本文时,Promise.prototype.finally还不是一个东西,然后它变成了一个东西……这可能发生在其他方法上,所以我建议你用resolvereject函数来增强promise实例:

function createDeferredPromise() {
let resolve
let reject


const promise = new Promise((thisResolve, thisReject) => {
resolve = thisResolve
reject = thisReject
})


return Object.assign(promise, {resolve, reject})
}

给别人的答案投票。

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.finally = this._promise.finally.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();

helper方法可以减轻这种额外的开销,并给您同样的jQuery感觉。

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

用法是

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

它类似于jQuery

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

不过,在用例中,这种简单的本机语法就可以了

return new Promise((resolve, reject) => {
displayConfirmationDialog({
confirm: resolve,
cancel: reject
});
});

我为此写了一个小库。https://www.npmjs.com/package/@inf3rno/promise.exposed

我使用了别人写的工厂方法,但我也覆盖了thencatchfinally方法,所以你也可以通过这些方法解决原始的承诺。

在没有外部执行人的情况下解决承诺:

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

从外部与执行器的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.");

我正在使用一个辅助函数来创建我所谓的“扁平承诺”-

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;


}

参见完整的工作示例-

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


})();

< p >编辑: 我已经创建了一个名为flat-promise的NPM包,代码也可用GitHub上.

您可以将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.

如何创建一个函数劫持拒绝并返回它?

function createRejectablePromise(handler) {
let _reject;


const promise = new Promise((resolve, reject) => {
_reject = reject;


handler(resolve, reject);
})


promise.reject = _reject;
return promise;
}


// Usage
const { reject } = createRejectablePromise((resolve) => {
setTimeout(() => {
console.log('resolved')
resolve();
}, 2000)


});


reject();

是的,你可以。通过在浏览器环境中使用CustomEvent API。以及在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>

I hope this answer is useful!

我已经把完成这项工作的要点放在一起:https://gist.github.com/thiagoh/c24310b562d50a14f3e7602a82b4ef13

以下是你应该如何使用它:

import ExternalizedPromiseCreator from '../externalized-promise';


describe('ExternalizedPromise', () => {
let fn: jest.Mock;
let deferredFn: jest.Mock;
let neverCalledFn: jest.Mock;
beforeEach(() => {
fn = jest.fn();
deferredFn = jest.fn();
neverCalledFn = jest.fn();
});


it('resolve should resolve the promise', done => {
const externalizedPromise = ExternalizedPromiseCreator.create(() => fn());


externalizedPromise
.promise
.then(() => deferredFn())
.catch(() => neverCalledFn())
.then(() => {
expect(deferredFn).toHaveBeenCalled();
expect(neverCalledFn).not.toHaveBeenCalled();
done();
});


expect(fn).toHaveBeenCalled();
expect(neverCalledFn).not.toHaveBeenCalled();
expect(deferredFn).not.toHaveBeenCalled();


externalizedPromise.resolve();
});
...
});
这里的许多答案与这篇文章中的最后一个例子类似。 我正在缓存多个promise,并且resolve()reject()函数可以分配给任何变量或属性。因此,我可以使这段代码更加紧凑:

function defer(obj) {
obj.promise = new Promise((resolve, reject) => {
obj.resolve = resolve;
obj.reject  = reject;
});
}

下面是一个使用此版本的defer()FontFace load 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

更新:如果你想封装对象,有2个选择:

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

首先在浏览器或节点上启用——allow-native -syntax

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


onClick = function () {
%ResolvePromise(p, value)
}


我创建了一个名为manual-promise的库,它的作用是替换Promise。这里的其他答案都不能作为Promise的替换,因为它们使用代理或包装器。

yarn add manual-promise

npn 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

公认的答案是错误的。使用作用域和引用非常简单,尽管这可能会让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);


我们实际上是在创建promise时获取resolve函数的引用,并返回该引用,以便可以在外部设置它。

在一秒钟内控制台将输出:

> foo

我发现自己在某些情况下也忽略了Deferred模式。你总是可以在ES6的顶部创建一个:

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

感谢每个在这篇文章中发帖的人。我创建了一个模块,其中包括前面描述的Defer()对象以及在其上构建的其他一些对象。它们都利用Promise和简洁的Promise回调语法在程序中实现通信/事件处理。

  • 延迟:可以远程解决失败的承诺(在承诺体之外)
  • 延迟:在给定时间后自动解决的承诺
  • TimeOut:承诺在给定时间后自动失败。
  • 循环:使用promise语法管理事件的可重新触发承诺
  • 队列:基于承诺链的执行队列。

rp = require("openpromise")

https://github.com/CABrouwers/openpromise https://www.npmjs.com/package/openpromise < / p >

只是另一种从外部解决承诺的方法

 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'

因为我没有找到我想要的东西,所以当我以这个问题结束时,我将分享我真正想要实现的东西。

场景:我有3个不同的API,具有相同的可能响应,因此我想在一个函数中处理承诺的完成和错误处理。这就是我所做的:

  1. 创建一个处理器函数:
  private handleHttpPromise = (promise: Promise<any>) => {
promise
.then((response: any) => {
// do something with the response
console.log(response);
})
.catch((error) => {
// do something with the error
console.log(error);
});
};
  1. 将承诺发送给创建的处理程序
  switch (method) {
case 'get': {
this.handleHttpPromise(apiService.get(url));
break;
}
case 'post': {
if (jsonData) {
this.handleHttpPromise(apiService.post(url, jsonData));
}
break;
}
// (...)
}

我想分享一些不同的东西,这是这个话题的延伸。

有时你需要一个“任务承诺”;在解析时自动在相同的地址(属性或变量)上重新创建。可以创建一个外部解析器来完成这个任务。

一个带有外部解析器的重复承诺的例子。无论何时调用解析器,都会在相同的地址/变量/属性处创建一个新的承诺。

let resolvePromise;
let thePromise;


const setPromise = (resolve) => {
resolvePromise = () => {
resolve();
thePromise = new Promise(setPromise);
}
}
thePromise = new Promise(setPromise);


(async () => {
let i = 0;
while (true) {
let msg = (i % 2 === 0) ? 'Tick' : 'Tock';
document.body.innerHTML = msg;
setTimeout(resolvePromise, 1000);
await thePromise;
i++;
}
})();

https://jsfiddle.net/h3zvw5xr

以防有人来找简化这个任务的util的typescript版本:

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

这可以用在。如:

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


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


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


类版本,在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)
}
}

如果(像我一样)你不喜欢扩展本机实例,也不喜欢笨拙的“.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"这样枯燥的东西