【问题标题】:ES6 promise settled callback?ES6 承诺解决回调?
【发布时间】:2015-09-02 20:01:03
【问题描述】:

无论我的 Promise 是否成功解决,我都想运行相同的操作。我不想将相同的函数绑定到.then 的两个参数。难道没有像 jQuery 那样的 .always 吗?如果没有,我该如何实现?

【问题讨论】:

标签: javascript ecmascript-6 es6-promise


【解决方案1】:

难道没有像 jQuery 那样的.always 吗?

不,there's not (yet)。虽然有一个active proposal,所以可能是 ES2018。
是的,有:promise .finally() 是自 ES2018 以来标准的一部分。

如果没有,我该如何实现?

您可以像这样自己实现finally 方法:

Promise.prototype.finally = function(cb) {
    const res = () => this
    const fin = () => Promise.resolve(cb()).then(res)
    return this.then(fin, fin);
};

或更广泛地,将分辨率信息传递给回调:

Promise.prototype.finally = function(cb) {
    const res = () => this
    return this.then(value =>
        Promise.resolve(cb({state:"fulfilled", value})).then(res)
    , reason =>
        Promise.resolve(cb({state:"rejected", reason})).then(res)
    );
};

两者都确保维持原始解决方案(当回调中没有异常时)并且等待承诺。

【讨论】:

  • 我认为 pollyfill 中 this.then 的错误回调应该抛出而不是返回。
  • @dfsq:它通过返回原始的、被拒绝的承诺来实现 :-)
  • 你是对的,this 是最初的承诺,因为=>,当然。
  • 你的第一个例子和described here有同样的问题,不是吗?不过,我不确定您的“更广泛”的解决方案。
  • 您的第一个实现(没有值)似乎与the spec 一致,这很好:-)
【解决方案2】:

使用 async/await,您可以组合 awaittry/finally,如下所示:

async function(somePromise) {
  try {
    await somePromise();
  } finally {
    // always run this-- even if `somePromise` threw something
  }
}

这是我使用 Babel 的 async-to-generator 插件在生产环境中运行的一个真实示例。

// Wrap promisified function in a transaction block
export function transaction(func) {
  return db.sequelize.transaction().then(async t => {
    Sequelize.cls.set('transaction', t);
    try {
      await func();

    } finally {
      await t.rollback();
    }
  });
}

我在 mocha 测试中使用此代码与 Sequelize ORM 一起启动 DB 事务,并且无论测试中 DB 调用的结果如何,总是在最后回滚。

这大致类似于 Bluebird 的 .finally() 方法,但在 IMO 中,语法要好得多!

(注意:如果你想知道为什么我在第一个 Promise 上没有 awaiting - 这是 Sequelize 的一个实现细节。它使用 CLS 来“绑定”一个到 Promise 链的 SQL 事务。任何在同一链内部引起的事务都被限定为事务。外部的任何内容都不是。因此,等待 Promise 将“关闭”事务块并破坏链。我将这个示例放入其中,向您展示如何将“vanilla”Promise 处理与异步函数混合在一起,并且可以很好地协同工作。)

【讨论】:

  • 你为什么不使用async function transaction 并放弃then 电话?
  • 整洁!从未想过将try/finallyasync/await 一起使用。这会派上用场的。
  • @Bergi - Sequelize 使用称为“CLS”的东西将事务块限定为 Promise 链。如果我使用await,它将返回一个事务处理程序,但随后的 SQL 将在块的 outside 中,因此不限于事务。这是 Sequelize 的一个实现细节。
【解决方案3】:

如果你不/不能更新原型,最终破解的方法是:

executeMyPromise()
.then(function(res){ return {res: res}; })
.catch(function(err){ return {err: err}; })
.then(function(data) {
    // do finally stuff
    if (data.err) {
        throw data.err;
    }
    return data.res;
}).catch(function(err) {
    // handle error
});

【讨论】:

  • 我不认为那是一样的。请参阅3rd and 4th points here。我们实际上可以在不修改原型的情况下使用 Bergi 的解决方案。你只需要说它很有趣:finallyFunc.call(thePromise, callback)。与绑定运算符配合得很好。
【解决方案4】:

这是我对 .finally() 的实现。

Promise.prototype.finally = function(cb) {
   return this.then(v=>Promise.resolve(cb(v)),
                    v=>Promise.reject(cb(v)));
};

我测试过:

(new Promise((resolve,reject)=>{resolve(5);})).finally(x=>console.log(x));  //5

(new Promise((resolve,reject)=>{reject(6);})).finally(x=>console.log(x));  //6

(new Promise((resolve,reject)=>{reject(7);}))
.then(x=>x,y=>y)
.catch(x=>{throw "error";}) 
.finally(x=>{console.log(x); throw "error"; return x;})  // 7
.then(x=>console.log(x),y=>console.log('e'));  //e
// Uncaught (in promise) undefined

【讨论】:

    【解决方案5】:

    总结:

    我们现在也可以访问Promise.prototype.finally()。这是一个可以作为最后一个元素放在 Promise 链上以执行一些清理的函数。与Promise.thenPromise.catch 相比,它的工作方式如下:

    • Promise.then 在 promise 被解决时被调用(如果你只把它放在第一个参数回调函数中)
    • Promise.catch 在 Promise 被拒绝时被调用
    • Promise.finally always 在 promise 完成时被调用,所以当 promise 被拒绝或解决时都会调用。

    示例:

    let Prom = new Promise((res, rej) => {
      let random = Math.random();
      
      if (random > 0.5) {
        res(1);
      } else {
        rej('Error occured')
      }
    
      
    });
    
    
    Prom.then((val) => {
      console.log(val);
      return val * 10;
    }).catch((err) => {
      console.log(err);
    }).finally(() => {
      console.log('finally executed');
    })

    在上面的例子中,我们可以观察到finally 总是被执行,无论promise 解决还是拒绝。并不是说finally 理想情况下应该始终位于 Promise 链的末尾以进行一些清理,无论 Promise 结果如何都应该执行。

    使用finally 的优点是它避免了代码重复的需要,因为它同时针对已解决和已拒绝的承诺执行。否则我们将不得不使用以下技巧:

    .then(onfullfilled, onfullfilled)
    

    .then(onfullfilled)
    .catch(onfullfilled)
    

    注意,现在我们必须将onfullfilled 函数定义为promisehandler 本身之外的命名函数(或者传入2 个更不优雅的匿名函数副本)。 Promise.finally 为我们解决了这个问题。

    【讨论】:

      【解决方案6】:

      无需引入新概念

      const promise = new Promise((resolve, reject) => {
        /*some code here*/
      });
      
      promise.then(() => {
        /* execute success code */
      }, () => {
        /* execute failure code here */
      }).then(() => {}, () => {}).then(() => {
        /* finally code here */
      });
      

      【讨论】:

      • 这会导致第一个 .then() 分支的返回值被丢弃,不是吗?
      【解决方案7】:

      延长Bergi答案。

      在 catch 处理程序中返回 Promise.reject() 将阻止最终调用 'then'。

      所以如果你要处理两次以上的 promise 错误,你应该使用这样的样板:

      return myPromise()
      .then(() => ... )
      .catch((error) => {
        ...
        myFinnaly();
        return Promise.reject(error);
      })
      .then(() => myFinnaly());
      

      【讨论】:

        【解决方案8】:

        allSettled 直接作为 finally:

        Promise.allSettled([promiseSuccess, promiseReject])
          .then(results => console.log);
        

        检查:https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-12-23
          • 1970-01-01
          • 1970-01-01
          • 2022-01-18
          • 2019-11-19
          • 1970-01-01
          • 1970-01-01
          • 2018-03-19
          相关资源
          最近更新 更多