Promise优于CPS和Continuation Functor/Monad有什么优势?

5 javascript asynchronous functional-programming promise es6-promise

ES6承诺

ES6 Promises是有限状态机,因此需要复杂的实现.除此之外,Promise/A +规范带来了许多粗糙的边缘:

  • 重载then(地图/链)
  • 递归展平/然后能够同化
  • 自动升降
  • 几个订户(多播)
  • 急切的评价

多播分发和急切评估是ES6承诺无法取消的原因之一.另外,我们不能添加具有特定功能的我们自己的层次,因为它们会立即被递归展平所吸收.

我很确定这些设计决策有很多很好的理由.但是,现在我们有一个不变的核心语言功能,而不是特定的竞争DSL,用于用户空间中的异步控制流.当然,互操作很重要,但是能够在不必考虑整个语言的向后兼容性的情况下发展异步控制流功能.

延续传球风格

继续传递样式从异步控制流中抽象出来,因为它摆脱了return语句.为了重新获得可组合性,我们在continuation的上下文中只需要一个仿函数:

const compk = (f, g) => x => k => f(x) (x => g(x) (k));


const inck = x => k => setTimeout(k, 0, x + 1);

const log = prefix => x => console.log(prefix, x);


compk(inck, inck) (0) (log("async composition:")); // 2
Run Code Online (Sandbox Code Playgroud)

当然,我们想要组成两个以上的功能.而不是手动编写compk3 = (f, g, h) => x => k => f(x) (x => g(x) (y => h(y) (k))) 等,需要一个程序化的解决方案:

const compkn = (...fs) => k => 
 fs.reduceRight((chain, f) => x => f(x) (chain), k);


const inck = x => (res, rej) => setTimeout(res, 0, x + 1);

const log = prefix => x => console.log(prefix, x);


compkn(inck, inck, inck) (log("async composing n functions:")) (0); // 3
Run Code Online (Sandbox Code Playgroud)

这种方法完全没有异常处理.让我们天真地调整常见的回调模式:

const compk = (f, g) => x => (res, rej) =>
 f(x) (x => g(x) (res), x => rej(x));

const compkn = (...fs) => (res, rej) =>
 fs.reduceRight((chain, f) => x => f(x) (chain, x => rej(x)), res);


const inc = x => x + 1;

const lift = f => x => k => k(f(x));

const inck = x => (res, rej) => setTimeout(res, 0, x + 1);

const decUIntk = x => (res, rej) =>
 setTimeout(x => x < 0 ? rej("out of range " + x) : res(x), 0, x - 1);

const log = prefix => x => console.log(prefix, x);


compk(decUIntk, inck) (0)
 (log("resolved with:"), log("rejected with:")); // rejected

compkn(inck, decUIntk, inck)
 (log("resolved with:"), log("rejected with:")) (0); // resolved
Run Code Online (Sandbox Code Playgroud)

这只是一个草图 - 必须投入大量精力才能实现正确的解决方案.但这是我猜的概念证明.compk/ compkn非常简单,因为他们不必战斗状态.

那么复杂的ES6承诺优于延续传递风格和相应的DSL(例如continuation functor/monad)有什么优势呢?

jib*_*jib 3

任何依赖函数组合的方法的缺点是惯用的 JavaScript 代码序列被命名函数列表替换。Promise 本身就受到了这个影响。

例如,我看到人们做我所说的回调精简版

let foo = () => Promise.resolve().then(() => console.log('foo'));
let bar = () => Promise.resolve().then(() => console.log('bar'));

foo().then(bar);
Run Code Online (Sandbox Code Playgroud)

这是一种方法,但不是唯一的方法,我个人不喜欢它,就像我不喜欢任何用英语或动作列表替换 JavaScript 的尝试一样。

对我来说,Promise 的一个好处是我们可以完全避免传统回调的间接性,并按照事情发生的顺序向前编写代码。箭头函数帮助:

Promise.resolve('foo')
  .then(foo => {
    console.log(foo);
    return Promise.resolve('bar');
  })
  .then(bar => {
    console.log(bar);
  });
Run Code Online (Sandbox Code Playgroud)

然而,这仍然可以说是一个行动清单。

所以对我来说,ES6 Promise 的一大优势是它们与async/await的兼容性,这让我们可以像同步代码一样为异步代码编写惯用的 JavaScript,尽管不是来自顶级范围(需要 Chrome 或 Firefox Beta):

(async () => {
  console.log(await Promise.resolve('foo'));
  console.log(await Promise.resolve('bar'));
})();
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,我很欣赏你的回答。但是,您暗示函数组合不是惯用的 Javascript。我不同意。社区只是不习惯而已。不常见与不惯用是不同的。因此,将“Promises”合并到语言中主要是一个美学决定,为异步控制流提供合成糖。虽然这是人类可以理解的,但它在某种程度上是有限制的(参见我的要点)。 (2认同)