承诺:不管resolve/reject如何执行某事?

Promises: Execute something regardless of resolve/reject?

使用 Promises 设计模式,是否可以实现以下内容:

 var a, promise

 if promise.resolve
     a = promise.responsevalue;

 if promise.reject
     a = "failed"

 AFTER resolution/rejection. Not ASYNC!!
     send a somewhere, but not asynchronously. //Not a promise

我要找的是 try - catch 情况下的 finally

PS:我在 NodeJS 上使用 ES6 Promise polyfill

注意: finally 现在是 JavaScript 承诺的标准部分,因此您可以这样做:

thePromise.then(result => doSomething(result)
          .catch(error => handleOrReportError(error))
          .finally(() => doSomethingAfterFulfillmentOrRejection());

finally之前的回答是标准的:


如果您 return 来自 catch 的值,那么您可以只对 catch.

的结果使用 then
thePromise.then(result => doSomething(result)
          .catch(error => handleErrorAndReturnSomething(error))
          .then(resultOrReturnFromCatch => /* ... */);

...但这意味着您正在将拒绝转化为实现(通过 returning 来自 catch 的内容而不是抛出或 returning 被拒绝的承诺),并依赖于这一事实。


如果你想要透明地传递 fulfillment/rejection 而不修改它的东西,那么 ES2015 ("ES6") 中没有任何东西可以做到这一点 (edit:又是现在),但是好写(这个是ES2015的,不过我下面有一个ES5的翻译):

{
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    Object.defineProperty(Promise.prototype, "finally", {
        value(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    });
}

示例:

{
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  Object.defineProperty(Promise.prototype, "finally", {
    value(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  });
}
test("p1", Promise.resolve("good")).finally(
  () => {
    test("p2", Promise.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

一些注意事项:

  1. 注意 this.constructor 的使用,这样我们就可以调用 resolve 任何类型的承诺(包括可能的子类)创建原始承诺;这与 Promise.resolve 和其他人的工作方式一致,并且是支持子类承诺的重要部分。

  2. 以上内容有意 包括 finally 回调的任何参数,并且没有指示承诺是被履行还是被拒绝,在为了与经典的try-catch-finally结构中的finally保持一致。但如果需要,可以轻松地将其中一些信息传递到回调中。

  3. 类似地,上面不使用 finally 回调 ed return 的值 除了 如果它是一个承诺,它在允许链继续之前等待承诺解决。

这是它的 ES5 翻译:

(function() {
    function worker(ctor, f, done) {
        return ctor.resolve(f()).then(done, done);
    }
    Object.defineProperty(Promise.prototype, "finally", {
        value: function(f) {
            var ctor = this.constructor;
            return this.then(
                function(result) {
                    return worker(ctor, f, function() {
                        return result;
                    });
                },
                function(error) {
                    return worker(ctor, f, function() {
                        throw error;
                    });
                }
            );
        }
    });
})();

示例:

(function() {
  function worker(ctor, f, done) {
    return ctor.resolve(f()).then(done, done);
  }
  Object.defineProperty(Promise.prototype, "finally", {
    value: function(f) {
      var ctor = this.constructor;
      return this.then(
        function(result) {
          return worker(ctor, f, function() {
            return result;
          });
        },
        function(error) {
          return worker(ctor, f, function() {
            throw error;
          });
        }
      );
    }
  });
})();

test("p1", Promise.resolve("good")).finally(function() {
  test("p2", Promise.reject("bad"));
});

function test(name, p) {
  return p.then(
      function(result) {
        console.log(name, "initial fulfillment:", result);
        return result;
      },
      function(error) {
        console.log(name, "initial rejection; propagating it");
        throw error;
      }
    )
    .finally(function() {
      console.log(name, "in finally");
    })
    .then(
      function(result) {
        console.log(name, "fulfilled:", result);
      },
      function(error) {
        console.log(name, "rejected:", error);
      }
    );
}

我认为这是将此功能集成到 ES5 中的 Promise polyfill 中的最简单方法。


或者如果你更喜欢继承 Promise 而不是修改它的原型:

let PromiseX = (() => {
    let worker = (p, f, done) => {
        return p.constructor.resolve(f()).then(done, done);
    };
    class PromiseX extends Promise {
        finally(f) {
            return this.then(
                result => worker(this, f, () => result),
                error  => worker(this, f, () => { throw error; })
            );
        }
    }
    PromiseX.resolve = Promise.resolve;
    PromiseX.reject = Promise.reject;

    return PromiseX;
})();

示例:

let PromiseX = (() => {
  let worker = (p, f, done) => {
    return p.constructor.resolve(f()).then(done, done);
  };
  class PromiseX extends Promise {
    finally(f) {
      return this.then(
        result => worker(this, f, () => result),
        error  => worker(this, f, () => { throw error; })
      );
    }
  }
  PromiseX.resolve = Promise.resolve;
  PromiseX.reject = Promise.reject;

  return PromiseX;
})();

test("p1", PromiseX.resolve("good")).finally(
  () => {
    test("p2", PromiseX.reject("bad"));
  }
);
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .finally(() => {
    console.log(name, "in finally");
  })
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}


您曾说过要在不扩展 Promise.prototype 子类的情况下执行此操作。在 ES5 中,效用函数使用起来 非常 笨拙,因为您必须向它传递要执行的 promise,这与正常的 promise 用法完全不一致。在 ES2015 中,可以做一些更自然的事情,但调用起来比修改原型或子类化更痛苦:

let always = (() => {
    let worker = (f, done) => {
        return Promise.resolve(f()).then(done, done);
    };
    return function always(f) {
        return [
            result => worker(f, () => result),
            error  => worker(f, () => { throw error; })
        ];
    }
})();

用法:

thePromise.then(...always(/*..your function..*/)).

注意扩展运算符的使用(这就是为什么这在 ES5 中不起作用),因此 always 可以为 then.

提供两个参数

示例:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", Promise.resolve("good")).then(...always(
  () => {
    test("p2", Promise.reject("bad"));
  }
));
function test(name, p) {
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}


在评论中,您表达了对 finally 不等待承诺的担忧;这又是最后一个 always 示例,延迟证明它确实如此:

let always = (() => {
  let worker = (f, done) => {
    return Promise.resolve(f()).then(done, done);
  };
  return function always(f) {
    return [
      result => worker(f, () => result),
      error  => worker(f, () => { throw error; })
    ];
  }
})();

test("p1", 500, false, "good").then(...always(
  () => {
    test("p2", 500, true, "bad");
  }
));

function test(name, delay, fail, value) {
  // Make our test promise
  let p = new Promise((resolve, reject) => {
    console.log(name, `created with ${delay}ms delay before settling`);
    setTimeout(() => {
      if (fail) {
        console.log(name, "rejecting");
        reject(value);
      } else {
        console.log(name, "fulfilling");
        resolve(value);
      }
    }, delay);
  });

  // Use it
  return p.then(
    result => {
      console.log(name, "initial fulfillment:", result);
      return result;
    },
    error => {
      console.log(name, "initial rejection; propagating it");
      throw error;
    }
  )
  .then(...always(() => {
    console.log(name, "in finally");
  }))
  .then(
    result => {
      console.log(name, "fulfilled:", result);
    },
    error => {
      console.log(name, "rejected:", error);
    }
  );
}

ES2015 代码:

promise.then(val => val).catch(() => "failed").then(a => doSomethigWithA(a));