【问题标题】:Is it safe to resolve a promise multiple times?多次解决承诺是否安全?
【发布时间】:2013-12-18 03:33:00
【问题描述】:

我的应用程序中有一个 i18n 服务,其中包含以下代码:

var i18nService = function() {
  this.ensureLocaleIsLoaded = function() {
    if( !this.existingPromise ) {
      this.existingPromise = $q.defer();

      var deferred = this.existingPromise;
      var userLanguage = $( "body" ).data( "language" );
      this.userLanguage = userLanguage;

      console.log( "Loading locale '" + userLanguage + "' from server..." );
      $http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
        $rootScope.i18n = translations;
        deferred.resolve( $rootScope.i18n );
      } );
    }

    if( $rootScope.i18n ) {
      this.existingPromise.resolve( $rootScope.i18n );
    }

    return this.existingPromise.promise;
  };

这个想法是用户会调用ensureLocaleIsLoaded 并等待承诺得到解决。但鉴于该函数的目的只是确保加载了语言环境,用户可以多次调用它。

我目前只存储一个承诺,如果用户在从服务器成功检索语言环境后再次调用该函数,则解决它。

据我所知,这是按预期工作的,但我想知道这是否是一种正确的方法。

【问题讨论】:

标签: javascript angularjs


【解决方案1】:

see github gist: reuse_promise.js

/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/

import React, { useEffect, useState } from 'react'

export default () => {
    
    const [somePromise, setSomePromise] = useState(promiseCreator())
        
    useEffect(() => {
        
        somePromise.then(data => {
            
            // do things here
            
            setSomePromise(promiseCreator())
        })
        
    }, [somePromise])
}

const promiseCreator = () => {
    return new Promise((resolve, reject) => {
        // do things
        resolve(/*data*/)
    })
}

【讨论】:

    【解决方案2】:

    据我目前了解的承诺,这应该是 100% 没问题的。唯一要了解的是,一旦解决(或拒绝),这就是延迟对象 - 它完成了。

    如果您再次根据其承诺调用then(...),您将立即获得(第一个)已解决/拒绝的结果。

    resolve() 的额外调用不会产生任何影响。

    以下是涵盖这些用例的可执行 sn-p:

    var p = new Promise((resolve, reject) => {
      resolve(1);
      reject(2);
      resolve(3);
    });
    
    p.then(x => console.log('resolved to ' + x))
     .catch(x => console.log('never called ' + x));
    
    p.then(x => console.log('one more ' + x));
    p.then(x => console.log('two more ' + x));
    p.then(x => console.log('three more ' + x));

    【讨论】:

    • 这是一个 JSBin,说明以上所有内容实际上都是正确的:jsbin.com/gemepay/3/edit?js,console 只有第一个解析被使用过。
    • 有没有人找到任何官方文档?通常不建议依赖未记录的行为,即使它现在有效。
    • ecma-international.org/ecma-262/6.0/#sec-promise.resolve - 到目前为止,我还没有发现任何表明它本质上是不安全的。如果你的处理程序做了一些真正应该只做一次的事情,我会让它在再次执行操作之前检查并更新一些状态。但我也想要一些官方 MDN 条目或规范文档来获得绝对清晰。
    • “你应该立即得到”,而不是立即/同步......它将在事件循环的下一个滴答声中 - (来自微任务队列)。
    • @demaniak 这个问题是关于Promises/A+,而不是 ES6 的承诺。但要回答您的问题,ES6 规范中关于无关的解析/拒绝安全的部分是 here
    【解决方案3】:

    没有。多次解决/拒绝承诺是不安全的。这基本上是一个难以捕捉的错误,因为它并不总是可重现的。

    有一种模式可用于在调试时跟踪此类问题。关于这个主题的精彩讲座:Ruben Bridgewater — 错误处理:做对了!(与问题相关的部分大约 40 分钟)

    【讨论】:

      【解决方案4】:

      您可以编写测试来确认行为。

      通过运行以下测试,您可以得出结论

      resolve()/reject() 调用永远不会抛出错误。

      一旦解决(拒绝),解决的值(拒绝错误)将被保留 不管后面是 resolve() 还是 reject() 调用。

      您也可以查看my blog post了解详情。

      /* eslint-disable prefer-promise-reject-errors */
      const flipPromise = require('flip-promise').default
      
      describe('promise', () => {
          test('error catch with resolve', () => new Promise(async (rs, rj) => {
              const getPromise = () => new Promise(resolve => {
                  try {
                      resolve()
                  } catch (err) {
                      rj('error caught in unexpected location')
                  }
              })
              try {
                  await getPromise()
                  throw new Error('error thrown out side')
              } catch (e) {
                  rs('error caught in expected location')
              }
          }))
          test('error catch with reject', () => new Promise(async (rs, rj) => {
              const getPromise = () => new Promise((_resolve, reject) => {
                  try {
                      reject()
                  } catch (err) {
                      rj('error caught in unexpected location')
                  }
              })
              try {
                  await getPromise()
              } catch (e) {
                  try {
                      throw new Error('error thrown out side')
                  } catch (e){
                      rs('error caught in expected location')
                  }
              }
          }))
          test('await multiple times resolved promise', async () => {
              const pr = Promise.resolve(1)
              expect(await pr).toBe(1)
              expect(await pr).toBe(1)
          })
          test('await multiple times rejected promise', async () => {
              const pr = Promise.reject(1)
              expect(await flipPromise(pr)).toBe(1)
              expect(await flipPromise(pr)).toBe(1)
          })
          test('resolve multiple times', async () => {
              const pr = new Promise(resolve => {
                  resolve(1)
                  resolve(2)
                  resolve(3)
              })
              expect(await pr).toBe(1)
          })
          test('resolve then reject', async () => {
              const pr = new Promise((resolve, reject) => {
                  resolve(1)
                  resolve(2)
                  resolve(3)
                  reject(4)
              })
              expect(await pr).toBe(1)
          })
          test('reject multiple times', async () => {
              const pr = new Promise((_resolve, reject) => {
                  reject(1)
                  reject(2)
                  reject(3)
              })
              expect(await flipPromise(pr)).toBe(1)
          })
      
          test('reject then resolve', async () => {
              const pr = new Promise((resolve, reject) => {
                  reject(1)
                  reject(2)
                  reject(3)
                  resolve(4)
              })
              expect(await flipPromise(pr)).toBe(1)
          })
      test('constructor is not async', async () => {
          let val
          let val1
          const pr = new Promise(resolve => {
              val = 1
              setTimeout(() => {
                  resolve()
                  val1 = 2
              })
          })
          expect(val).toBe(1)
          expect(val1).toBeUndefined()
          await pr
          expect(val).toBe(1)
          expect(val1).toBe(2)
      })
      
      })
      

      【讨论】:

        【解决方案5】:

        没有明确的方法可以多次解决 Promise,因为它已经解决了。这里更好的方法是使用观察者可观察模式,例如我编写了以下代码来观察套接字客户端事件。您可以扩展此代码以满足您的需要

        const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
            const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
            const Observable = function (fn) {
                const subscribers = [];
                this.subscribe = subscribers.push.bind(subscribers);
                const observer = {
                    next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
                };
                setTimeout(() => {
                    try {
                        fn(observer);
                    } catch (e) {
                        subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
                    }
                });
        
            };
        
            const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
        
            fromEvent(client, 'document:save').subscribe({
                async next(document, docName) {
                    await writeFilePromise(resolve(dataDir, `${docName}`), document);
                    client.emit('document:save', document);
                }
            });
        

        【讨论】:

          【解决方案6】:

          如果需要更改promise的返回值,只需在then中返回新值,并在其上链下一个then/catch

          var p1 = new Promise((resolve, reject) => { resolve(1) });
              
          var p2 = p1.then(v => {
            console.log("First then, value is", v);
            return 2;
          });
              
          p2.then(v => {
            console.log("Second then, value is", v);
          });

          【讨论】:

            【解决方案7】:

            你应该做的是在你的主要 ng-outlet 上放置一个 ng-if 并显示一个加载微调器。加载语言环境后,您将显示插座并让组件层次结构呈现。这样,您的所有应用程序都可以假定已加载语言环境并且不需要检查。

            【讨论】:

              【解决方案8】:

              我前段时间遇到过同样的事情,确实一个promise只能解决一次,另一个尝试什么都不做(没有错误,没有警告,没有then调用)。

              我决定像这样解决它:

              getUsers(users => showThem(users));
              
              getUsers(callback){
                  callback(getCachedUsers())
                  api.getUsers().then(users => callback(users))
              }
              

              只需将您的函数作为回调传递,并根据需要多次调用它!希望这是有道理的。

              【讨论】:

              • 我认为这是错误的。您可以简单地从getUsers 返回承诺,然后根据需要多次调用该承诺上的.then()。无需传递回调。在我看来,promises 的优点之一是你不需要预先指定回调。
              • @JohnHenckel 这个想法是多次解析promise,即多次返回数据,而不是多个.then语句。对于它的价值,我认为将数据多次返回到调用上下文的唯一方法是使用回调而不是承诺,因为承诺不是以这种方式工作的。
              猜你喜欢
              • 2014-05-27
              • 2018-03-07
              • 2015-12-07
              • 1970-01-01
              • 2021-08-17
              • 2015-06-24
              • 2016-12-23
              • 2019-09-29
              相关资源
              最近更新 更多