Promises优于CPS和Continuation Functor / Monad有什么优势? [英] What are the advantages of Promises over CPS and the Continuation Functor/Monad?

查看:140
本文介绍了Promises优于CPS和Continuation Functor / Monad有什么优势?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ES6承诺



ES6承诺是有限状态机,因此需要复杂的实现。除此之外,Promise / A +规范带有很多粗糙的优势:


  • 超载然后(map / chain)

  • 递归拼合/随后同化

  • 自动提升
  • 几个订阅者(多播)

  • 热切评价 b
    组播分发和热切评估包括ES6承诺无法取消的原因。另外,我们不能添加具有特定功能的自己的图层,因为它们会立即通过递归拼合来同化。



    我很确定有很多任何这些设计决定的充分理由。然而,现在我们拥有一个不变的核心语言特性,而不是具体的竞争DSL用于用户空间中的异步控制流。当然,互操作很重要,但在不考虑整个语言的后向兼容性的情况下,发展异步控制流功能的能力也是如此。

    继续传递风格 h1>

    从异步控制流继续传递样式摘要,因为它消除了 return 语句。为了重新获得组合性,我们只需要在继续的情况下使用一个函子:

    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



    当然,我们要编写两个以上功能。而不是手动写入 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



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



    =(f,g )=> x => (res,rej)=> f(x)(x => g(x)(res),x => rej(x)); const compkn =(... fs)=> (res,rej)=> (链,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:)); / / rejectcompkn(inck,decUIntk,inck)(log(resolved with:),log(rejected with:))(0); //解析



    这只是一个简图 - 很多努力将不得不投入实现适当的解决方案。但这是我猜想的一个概念证明。 compk / compkn 非常简单,因为它们不必打斗。



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

    解决方案

任何依赖于功能组合的方法的缺点是,惯用的JavaScript代码序列被替换为命名函数列表。承诺自己受此影响。

我看到有人做我称之为 callback lite

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

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



对我来说,promise的好处是我们可以完全避免传统回调的间接方式,并按事情发生的顺序编写代码,在向前的方向。 Arrow功能帮助:

Promise.resolve('foo').then foo => {console.log(foo); return Promise.resolve('bar');}).then(bar => {console.log(bar);});



然而,这可以说仍然是一个动作列表。



对我来说,ES6承诺的一大优势是它们与 async / await ,这让我们可以编写异步代码的惯用JavaScript,就像我们将同步代码一样,尽管不是(需要Chrome或Firefox Beta版本):


 (async()=> {console.log(等待Promise.resolve('foo')); console.log(等待Promise.resolve('bar'));})();  

ES6 Promises

ES6 Promises are finite state machines and thus require complex implementations. Beyond that the Promise/A+ spec comes with a lot of rough edges:

  • overloaded then (map/chain)
  • recursive flattening / then-able assimilation
  • automatic lifting
  • several subscribers (multicast)
  • eager evaluation

Multicast distribution and eager evaluation are among other things the reasons why ES6 promises cannot be cancelled. Additionally, we can't add our own layers of then-ables with specific features, because they are immediately assimilated by recursive flattening.

I am pretty sure there were a lot of good reasons for any of these design decisions. However, now we have an invariable core language feature rather than specific, competing DSLs for asynchronous control flows in userland. Surely, interop is important, but so is the ability to evolve async control flow features without having to take backward compatibility of the whole language into consideration.

Continuation Passing Style

Continuation passing style abstracts from asynchronous control flows, since it gets rid of the return statement. To regain composability we merely need a functor in the context of continuations:

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

Of course, we want to compose more than two functions. Instead of manually writing compk3 = (f, g, h) => x => k => f(x) (x => g(x) (y => h(y) (k))) etc., a programmatic solution is desired:

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

This approach completely lacks exception handling. Let's naively adapt the common callback pattern:

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

This is just a sketch - a lot of effort would have to be invested to achieve a proper solution. But it is a proof of concept I guess. compk/compkn are extremely simple, because they don't have to fight state.

So what are the advantages of complex ES6 promises over continuation passing style and corresponding DSLs like the continuation functor/monad?

解决方案

The disadvantage of any approach that relies on functional composition is that idiomatic JavaScript code sequences are replaced with lists of named functions. Promises themselves suffer from this.

E.g. I see people do what I call callback lite:

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

foo().then(bar);

This is one approach, but not the only one, and I personally dislike it, the same way I dislike any attempt at replacing JavaScript with English or action lists.

To me, a benefit of promises is that we can avoid the indirection of traditional callbacks altogether and write code in the order things happen, in a forward direction. Arrow functions help:

Promise.resolve('foo')
  .then(foo => {
    console.log(foo);
    return Promise.resolve('bar');
  })
  .then(bar => {
    console.log(bar);
  });

However, this is arguably still an action list.

So to me, the big advantage of ES6 promises is their compatibility with async/await, which let us write idiomatic JavaScript for asynchronous code much like we would synchronous code, albeit not from top-level scope (requires Chrome or Firefox Beta):

(async () => {
  console.log(await Promise.resolve('foo'));
  console.log(await Promise.resolve('bar'));
})();

这篇关于Promises优于CPS和Continuation Functor / Monad有什么优势?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆