528

通常,a承诺构造和使用如下:

新承诺((解决,拒绝)=>{const obj=新的MyEventEmitter();obj.onsuccess=(event)=>{resolve(event.result);};obj.onerror=(event)=>{拒绝(event.error);};});

但最近,为了灵活性,我一直在这样做,将解析器置于执行器回调之外:

让外部解决;让外部拒绝;new Promise((解决,拒绝)=>{outside解析=解析;outsideReject=拒绝;});

之后:

onClick=函数(){outsideResolve();}

这很好,但有更简单的方法吗?如果不是,这是一个好做法吗?

6
  • 我认为没有其他方法。我相信已经指定回调传递给承诺必须同步执行,以允许“导出”这两个函数。 评论 2014年10月1日20:44
  • 1
    这和你写的完全一样。就我而言,这是“规范”的方式。 评论 2016年7月31日8:40
  • 51
    我认为将来应该有一种正式的方式来实现这一点。在我看来,这个功能非常强大,因为你可以等待来自其他上下文的值。
    – 约瑟
    评论 2018年4月3日12:47
  • 1
    我认为Promise API“建议”始终将其用作返回值,而不要将其用作可以访问或调用的对象。换句话说,迫使我们将它们视为返回值,而不是我们可以访问的对象或可以调用的函数,或者我们可以用变量引用或作为参数传递的东西。如果您开始将promises用作任何其他对象,那么您可能最终需要像您的问题一样从外部解决它。。。话虽如此,我也认为应该有一种正式的方式来这样做。。。而递延似乎只是我的一个变通方法。
    – 癌细胞
    评论 2019年5月14日7:44
  • @Jose对我来说,有几次它很有用,当我需要一个Promise来在另一个诺言之后立即解决,以防Promise.all或Promise.race不起作用。 评论 2021年3月16日21:51

22答案22

重置为默认值
267

简单:

var promiseResolve,promiseReject;var promise=新promise(函数(resolve,reject){promiseResolve=解决;promiseReject=拒绝;});promiseResolve();
8
  • 4
    @正如公认的答案所提到的那样,它是故意这样设计的。关键是,如果抛出异常,它将被promise构造函数捕获。这个答案(和我的答案一样)有一个陷阱,可能会为任何代码调用抛出异常承诺解决()承诺的语义是总是返回一个值。此外,这在功能上与OP的帖子相同,我不知道这是在以可重用的方式解决什么问题。 评论 2016年6月27日20:30
  • 5
    @我不确定你说的是不是真的。调用的代码承诺解决()不会引发异常。您可以定义.捕捉在构造函数上,无论什么代码调用它,构造函数的.捕捉将被调用。以下是jsbin演示了其工作原理:jsbin.com/yicerewivo/edit?js,控制台
    – 卡特
    评论 2016年7月5日22:26
  • 是的,它之所以被捕获,是因为你在它周围包了另一个promise构造函数——这正是我想要表达的观点。然而,假设您有一些其他代码试图在构造函数外调用resolve()(也称为Deferred对象)。。。它可能会引发异常而不会被捕获jsbin.com/cokiqiwapo/1/edit?js,控制台 评论 2016年7月6日13:26
  • 13
    我甚至不确定这是一个糟糕的设计。在承诺之外抛出的错误不应该在承诺之内被发现。这可能是一个误解或错误理解的例子,如果设计师真的期望要捕获的错误。
    – 卡勒
    评论 2017年5月11日22:17
  • 65
    这个确切的结构已经在问题中提到了。你读过吗? 评论 2017年9月30日15:08
168

在这里参加聚会有点晚,但另一种方法是使用推迟对象。基本上,您有相同数量的样板文件,但如果您想传递它们并可能在它们的定义之外进行解析,这很方便。

天真的实施:

类延期{构造函数(){this.promise=新承诺((解决,拒绝)=>{this.reject=拒绝this.resolve=解决})}}函数asyncAction(){var dfd=新的延迟()setTimeout(()=>{dfd.解析(42)}, 500)返回dfd.承诺}asyncAction().then(结果=>{console.log(结果)//42})

ES5版本:

函数Deferred(){var self=这个;this.promise=新承诺(功能(解决、拒绝){self.reject=拒绝self.resolve=解决})}函数asyncAction(){var dfd=新的延迟()setTimeout(函数(){dfd.解析(42)}, 500)返回dfd.承诺}asyncAction().then(函数(结果){console.log(结果)//42})
10
  • 1
    请注意这里的词汇范围。
    – 星云
    评论 2016年2月2日20:45
  • 1
    在是否解决|拒绝按词汇或通过绑定。这只是一个简单的jQuery延迟自1.0以来一直存在的对象(ish)。它的工作原理和承诺完全一样,只是没有扔保险。这个问题的重点是如何在创建承诺时节省几行代码。 评论 2016年7月13日0:08
  • 4
    使用延迟是通常的方法,我不知道为什么这个值不高 评论 2017年2月28日22:21
  • 2
    回答得很好!正在寻找jQuery提供的延迟功能。 评论 2017年3月28日22:16
  • 2
    推迟不赞成? 评论 2017年10月16日1:34
127

从ECMAScript 2024起–是的,使用承诺。withResolvers().

对于早期版本,没有其他方法可以做到这一点,但我可以说,这个用例并不常见。正如Felix在评论中所说,你所做的将始终如一。

值得一提的是,promise构造函数这样做的原因是通过安全性——如果在promise构造器中运行代码时发生了您没有预料到的异常,它将变成拒绝,这种抛出安全性&将抛出的错误转换为拒绝非常重要,有助于维护可预测的代码。

出于这个安全考虑,执行者回调被选择为优先于延迟(这是一种允许您所做事情的替代承诺构建方式)——至于最佳实践,我会传递元素并使用承诺构造函数:

const p=新承诺((解决,拒绝)=>{this.onclick=解决;});

因此,无论何时可以在导出函数时使用promise构造函数——我建议你使用它。只要你能避免这两种情况,就避免这两个和chain。

15
  • 2
    你好,本杰明!如果我们还不知道承诺什么时候能实现,那么目前是否没有更好的方式来获得美味的承诺糖?就像某种异步等待/通知模式? 例如,“store”,然后调用承诺链条?例如,在我的特定情况下,我在服务器上等待特定的客户端回复(一种SYN-ACK握手,以确保客户端成功更新状态)。
    – 多米
    评论 2015年5月3日13:17
  • 2
    如何使用fetch API实现同样的功能? 评论 2017年4月20日5:05
  • 155
    不常见?我几乎每个项目都需要它。 评论 2017年6月23日15:24
  • 2
    至于用例,请考虑在触发事件和发生其他事情之后需要做一些事情。你想把事件变成一个承诺,并把它和另一个承诺结合起来。我觉得这是一个普遍的问题。
    – 盖尔曼
    评论 2017年10月24日8:31
  • 6
    如果你能var p=新承诺();p.解决() 评论 2021年4月14日8:30
38

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

如果你绑定然后抓住然后推迟对象,然后它完全实现承诺API,您可以将其视为承诺等待等等。

⚠️ 编者注:我不再推荐这种模式,因为在写作时,承诺.原型.最终还不是一件事,然后就变成了一件事……这可能会发生在其他方法上,所以我建议您使用决定拒绝改为函数:

函数createDeferredPromise(){让我们解决让拒绝const promise=新promise((thisResolve,thisReject)=>{resolve=此解决拒绝=thisReject})return Object.assign(promise,{resolve,reject})}

去投票支持别人的答案。

类DeferredPromise{构造函数(){这个_诺言=新诺言((解决,拒绝)=>{//将解析和拒绝函数分配给“this”`//使它们在类实例上可用this.resolve=解决;this.reject=拒绝;});//绑定`then`和`catch`以实现与Promise相同的接口this.then=这个_承诺。然后。绑定(this.promise);this.catch=这个_promise.catch.bind(this.promise);this.finally=这个_promise.finally.bind(this.promise);this[Symbol.toStringTag]=“承诺”;}}const deferred=新的DeferredPromise();console.log('等待2秒…');setTimeout(()=>{延迟。解决(“哇!”);}, 2000);异步函数someAsyncFunction(){const值=等待延迟;console.log(值);}someAsyncFunction();

4
  • 我真的很喜欢这个。谢谢您。我在我的Express应用程序中使用它作为一个自定义组件,但如果您愿意创建一个NPM模块,它将非常棒,如果需要的话,我也可以。这种方法是新的async/await和旧的Parse平台如何实现承诺的完美组合en.wikipedia.org/wiki/Parse_(平台) 评论 2020年8月2日18:44
  • 1
    别忘了承诺.原型.最终. 评论 2021年11月8日17:31
  • 很好,我已经有一分钟没有看到这个答案了,我不再推荐这个了。我已经更新了答案以反映 评论 2021年11月8日20:31
  • 如果你担心未来承诺,还可以通过循环遍历承诺没有? 评论 2021年11月8日20:50
31

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

函数createPromise(处理程序){var解析,拒绝;var promise=新promise(函数(_resolve,_reject){解决=重新解决;拒绝=拒绝;if(处理程序)处理程序(解析、拒绝);})promise.resolve=解决;promise.reject=拒绝;回报承诺;}//创建var promise=创建承诺()promise.then(函数(数据){alert(数据)})//从外部解决承诺.解决(200)
  • 5
    谢谢,这很管用。但什么是处理程序?我必须把它拆下来才能让它工作。
    – 萨希德
    评论 2019年2月1日5:52
  • @Sahid当您运行createPromise()时,您需要将函数作为参数传递给它。否则代码将无法工作。您可以使用if语句,并在调用它之前检查处理程序参数的有效性。 评论 2020年9月15日18:22
  • 谢谢你的代码!但是其他代码不可能调用您的.解决在回调设置之前?我习惯于常规线程,而不是异步事件,所以我可能有点困惑。
    – 腺嘌呤
    评论 2020年10月29日1:22
23

公认的答案是错误的。使用范围和引用很容易,尽管它可能会产生Promise纯粹主义者愤怒:

常数createPromise=()=>{let解析器;返回[新承诺((解决,拒绝)=>{resolver=解析;}),分解器,];};const[promise,resolver]=createPromise();promise.then(value=>console.log(value));setTimeout(()=>解析器('foo'),1000);

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

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

>foo公司
4
  • 我认为这是最好的方法。唯一的问题是代码可以稍微少一些冗长。 评论 2019年9月12日11:52
  • 很好!聪明的想法+如果可以的话,我会给你50英镑。
    – 米提亚
    评论 2020年3月12日14:59
  • 这正是OP所做的。事实上,您正在将延迟模式重新引入Promises,当然这是可能的,并且您的方法有效(作为初始OP代码),但这不是最佳实践,因为接受的答案中描述了“抛出安全原因”。 评论 2020年5月21日2:37
  • @Mitya我是说,能够如果你真的想
    – 米肯32
    评论 5月3日18:08
17

以防有人来寻找简化此任务的util的TypeScript版本:

导出常量递延=<T>()=>{让我们解决!:(值:T|PromiseLike<T>)=>无效;让我们拒绝!:(原因?:未知)=>无效;const promise=新promise<T>((res,rej)=>{解析=res;拒绝=拒绝;});返回{解决、拒绝、承诺};};

可以这样使用:

const{promise,resolve}=deferred<string>();promise.then((值)=>console.log(值));//没有什么解析('foo');//console.log:foo
16

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

函数Deferred(){让决心;让拒绝;const promise=新promise((res,rej)=>{解析=res;拒绝=拒绝;});返回{承诺,解决,拒绝};}

用法是

const{promise,resolve,reject}=延迟();显示确认对话框({确认:解决,取消:拒绝});回报承诺;

这与jQuery类似

常数dfd=$。延期();显示确认对话框({确认:dfd.解决,取消:dfd.拒绝});return dfd.promise();

尽管在用例中,这种简单的本机语法很好

return new Promise((resolve,reject)=>{显示确认对话框({确认:解决,取消:拒绝});});
13

我使用一个helper函数来创建我称之为“简单承诺”的内容-

函数flatPromise(){让决心,拒绝;const promise=新的promise((res,rej)=>{解析=res;拒绝=拒绝;});返回{承诺、解决、拒绝};}

我就是这样用的-

函数doSomethingAsync(){//得到你的承诺和回电const{resolve,reject,promise}=flatPromise();//做一些令人惊奇的事情。。。setTimeout(()=>{解决(“one!”);}, 500);//把你的承诺传递给世界回报承诺;}

参见完整的工作示例-

函数flatPromise(){让决心,拒绝;const promise=新promise((res,rej)=>{解析=res;拒绝=拒绝;});返回{承诺、解决、拒绝};}函数doSomethingAsync(){//得到你的承诺和回电const{resolve,reject,promise}=flatPromise();//做一些令人惊奇的事情。。。setTimeout(()=>{下定决心(“下定决心!”);}, 500);//把你的承诺传递给世界回报承诺;}(异步函数run(){const result=等待doSomethingAsync().catch(err=>console.error('rejected with',err));console.log(结果);})();

编辑:我创建了一个名为扁平防护代码也可用在GitHub上.

12

你可以在课堂上把诺言包装起来。

类延期{构造函数(处理程序){this.promise=新承诺((解决,拒绝)=>{this.reject=拒绝;this.resolve=解决;处理程序(解析、拒绝);});this.promise.resolve=本解决方案;this.promise.reject=此.reject;返回this.promise;}承诺;解决;拒绝;}//如何使用。const promise=new Deferred((解析,拒绝)=>{//像正常的承诺一样使用。});promise.resolve();//从任何上下文进行解析。
10

在某些情况下,我发现自己也错过了延迟模式。您始终可以在ES6承诺的基础上创建一个:

导出默认类递延<T>{私有解析:(值:T)=>void=()=>{};私有对象:(值:T)=>void=()=>{};private _promise:承诺<T>=新承诺<T>((解决,拒绝)=>{这个_拒绝=拒绝;这个_resolve=解决;})public get promise():承诺{返回这个_承诺;}公共解析(值:T){这个_解析(值);}公共拒绝(值:T){这个_拒绝(价值);}}
1
  • 1
    我喜欢这个。我只需将签名从拒绝更改为拒绝(原因:任何) 评论 2021年2月24日8:06
9

这里的许多答案与中的最后一个示例类似这篇文章.我缓存了多个Promiseresolve()拒绝()函数可以分配给任何变量或属性。因此,我能够使此代码稍微紧凑:

函数defer(obj){obj.promise=新承诺((resolve,reject)=>{obj.resolve=解决;obj.reject=拒绝;});}

下面是使用此版本的延迟()字体字体使用另一个异步进程加载Promise:

DOMContentLoaded(evt)上的函数{让所有=[];//一系列承诺全局={};//其他地方使用的全局对象推迟(glob);all.push(全球承诺);//使用callback=resolveGlob()启动异步进程const myFont=新FontFace(“myFont”,“url(myFont.woff2)”);document.fonts.add(myFont);myFont.load();all.push[myFont];Promise.all(all).then(()=>{runIt();},(v)=>{alert(v);});}//...函数resolveGlob(){glob.resolve();}函数runIt(){}//在所有promise解析后运行

更新:如果要封装对象,有两个备选方案:

函数defer(obj={}){obj.promise=新承诺((resolve,reject)=>{obj.resolve=解决;obj.reject=拒绝;});返回对象;}let deferred=defer();

类延期{构造函数(){this.promise=新承诺((解决,拒绝)=>{this.resolve=解决;this.reject=拒绝;});}}let deferred=新的deferred();
1
  • 如果您在异步函数中使用这些示例,那么当您想要使用已解析承诺的值时,需要参考承诺属性:const result=wait deferred.promise;
    – b00吨
    评论 2020年5月9日5:42
6

在不久的将来,我们将能够使用本机承诺。withResolvers(),现在位于第4阶段已在基于Blink/WebKit的浏览器中实现在撰写本文时。

同时,我使用了一个具有相同签名的实用函数:

导出默认函数usePromise(){常量noop=()=>{};let localResolve:(value:T|PromiseLike<T>)=>void=noop;let-localReject:(原因?:未知)=>void=noop;const promise=新promise(解决,拒绝)=>{localResolve=解析;localReject=拒绝;});返回{承诺,解析:localResolve,拒绝:localReject,};}//用法const{promise,resolve,reject}=usePromise();//示例const triggerEl=document.querySelector('.trigger');const someEl=document.querySelector('.dummy-element');const{promise,resolve}=usePromise();someEl.addEventListener('transitionend',()=>{resolve();});triggerEl.addEventListener('click',async()=>{someEl.classList.add('fadeout');等待承诺;//在移除元件之前,等待过渡结束someEl.remove();});

值得一提的是,这也是一个有效的Vue3可组合。

4

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

以下是模式:

函数getPromise(){变量_resolve,_reject;var promise=新promise((resolve,reject)=>{_拒绝=拒绝;_resolve=解决;});promise.resolve_ex=(值)=>{_解析(值);};promise.reject_ex=(值)=>{_拒绝(价值);};回报承诺;}

使用它:

var promise=getPromise();承诺。然后(值=>{console.info(“承诺已经实现:”+值);});promise.resolve_ex(“你好”);//或拒绝的版本//promise.reject_ex(“再见”);
5
  • 伟大的。。。我只是在学习承诺,但一直对你似乎无法在“其他地方”解决这些问题感到困惑。使用闭包来隐藏实现细节是个好主意。。。但事实上,我不确定你做了什么:我确信有一种方法可以完全地隐藏不可访问的变量。。。这就是闭包的真正含义。。。 评论 2017年7月3日18:05
  • >闭包是可以通过访问封闭范围的变量来引用(和传递)的代码块。变量_resolve,_reject;是封闭范围。 评论 2017年7月3日20:12
  • 是的,很公平。实际上,在我看来,我的答案过于复杂,而且你的答案可以简化:你只需要去promise.resolve_ex=重新解决;promise.reject_ex=拒绝;……仍能正常工作。 评论 2017年7月3日21:03
  • "附加一个函数来扩展承诺本身。“-不要这样做。承诺是结果值,它们不应该提供解决这些问题的能力。你不想传递那些扩展的承诺。
    – 贝吉
    评论 2017年7月4日16:41
  • 2
    问题是如何解决超出范围的问题。这是一个可行的解决方案,在我们的生产中,我们确实有必要这样做。我不明白为什么解决所述问题值得投反对票。 评论 2017年7月4日20:46

类版本,在Typescript中:

导出类递延<T>{公开只读承诺:承诺私有resolveFn!:(值:T|PromiseLike<T>)=>无效私人拒绝Fn!:(原因?:任何)=>无效公共构造函数(){this.promise=新承诺{this.resolveFn=解决this.rejectFn=拒绝})}公众拒绝(原因?:任何):无效{this.rejectFn(理由)}公共解析(param:T):void{this.resolveFn(参数)}}
2

是的,你可以。通过使用自定义事件浏览器环境的API。并在node.js环境中使用事件发射器项目。由于问题中的代码片段是针对浏览器环境的,因此这里有一个相同的工作示例。

函数myPromiseReturningFunction(){return new Promise(解决=>{window.addEventListener(“myCustomEvent”,(event)=>{解决(event.detail);}) })}myPromiseReturningFunction()。然后(结果=>{警报(结果)})document.getElementById(“p”).addEventListener(“点击”,()=>{window.dispatchEvent(新的CustomEvent(“myCustom事件”,{detail:“它可以工作!”})})
单击我</p>

我希望这个答案有用!

0
1

我发现这个问题最简洁的解决方案是[承诺。withResolvers()].1

const{promise,resolve,reject}=promise.withResolvers();

该函数返回允许从执行器函数外部调用的对象。

0

我为此写了一个小库。https://www.npmjs.com/package网站/@信息3rno/promise.exposed

我使用了别人写的工厂方法,但我覆盖了然后,抓住,最后方法也一样,因此您也可以通过这些方法来解决最初的承诺。

无外部执行人解决承诺:

const promise=promise.exposed().then(console.log);promise.resolve(“这应该显示在控制台中。”);

从外部与执行者的setTimeout竞争:

const promise=promise.exposed(函数(resolve,reject){setTimeout(函数(){决心(“我几乎睡着了。”)}, 100000);}).then(console.log);setTimeout(函数(){诺言。决心(“我不想等那么久。”);}, 100);

如果您不想污染全局命名空间,则存在无冲突模式:

const createExposedPromise=require(“@inf3rno/promise.exposed/noConflict”);const promise=createExposedPromise().then(console.log);promise.resolve(“这应该显示在控制台中。”);
0

我建了一个图书馆叫手动承诺它可以替代承诺。这里的其他答案都不能替代承诺,因为它们使用代理或包装器。

纱线添加手工承诺

npn安装手册承诺

从“manual-promise”导入{manual-promise};const prom=新的ManualPromise();prom.解决(2);//行动仍然可以在承诺范围内进行const prom2=新的ManualPromise((解析,拒绝)=>{// ... 代码});新的ManualPromise()Promise实例===true

https://github.com/zpxp/manual-promise#自述文件

0

只是解决外部承诺的另一个解决方案

类锁定{#锁定;//承诺得到解决(发布时)释放;//释放锁id;//锁的Id构造函数(id){this.id=id这个#lock=新承诺((解决)=>{this.release=()=>{if(解决){resolve()}其他{承诺.resolve()}}})}get(){return this.#lock}}

用法

let lock=新锁(…some id…);...lock.get().then()=>{console.log('已解决/已释放')})lock.release()//排除“已解析/已释放”
0

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

有时,您希望在解析时在同一地址(属性或变量)自动重新创建“任务承诺”。可以创建一个外部解析器来实现这一点。

使用外部解析器的重复承诺示例。每当调用解析器时,都会在相同的地址/变量/属性上创建一个新的承诺。

让resolvePromise;让承诺实现;const setPromise=(解析)=>{resolvePromise=()=>{resolve();thePromise=新的Promise(setPromise);}}thePromise=新的Promise(setPromise);(异步()=>{设i=0;while(真){让消息=(i%2===0)?'勾选:“Tock”;document.body.innerHTML=消息;setTimeout(resolvePromise,1000);等待承诺;i++;}})();

https://jsfiddle.net/h3zvw5xr

0

如果(像我一样)你不喜欢增加本机实例,也不喜欢笨拙“.承诺”属性。。。但是,如果你喜欢代理和修改类,那么这一个是为你准备的:

类GroovyPromise{构造函数(){return new Proxy(new Promise((resolve,reject)=>{this.resolve=解决;this.reject=拒绝;}), {获取:(target,prop)=>this[prop]|target[prop].bind(target),});}}

这样使用:

const groovypromise=新groovypromise();setTimeout(()=>groovypromise.resolve('groovy'),1000);console.log(等待groovypromise);

当然,您也可以将类重命名为类似这样的枯燥内容“延期”

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.