ES6承诺
ES6 Promises是有限状态机,因此需要复杂的实现.除此之外,Promise / A规范带来了许多粗糙的边缘:
>重载然后(地图/链)
>递归压扁/然后能够同化
>自动升降
>几个订户(多播)
>急切的评价
多播分发和急切评估是ES6承诺无法取消的原因之一.另外,我们不能添加具有特定功能的我们自己的层次,因为它们会立即被递归展平所吸收.
我很确定这些设计决策有很多很好的理由.但是,现在我们有一个不变的核心语言功能,而不是特定的竞争DSL,用于用户空间中的异步控制流.当然,互操作很重要,但是能够在不必考虑整个语言的向后兼容性的情况下发展异步控制流功能.
延续传球风格
连续传递样式从异步控制流中抽象出来,因为它摆脱了return语句.为了重新获得可组合性,我们在continuation的上下文中只需要一个仿函数:
const compk = (f,g) => x => k => f(x) (x => g(x) (k));
const inck = x => k => setTimeout(k,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,x);
compkn(inck,inck,inck) (log("async composing n functions:")) (0); // 3
这种方法完全没有异常处理.让我们天真地调整常见的回调模式:
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,x + 1);
const decUIntk = x => (res,rej) =>
setTimeout(x => x < 0 ? rej("out of range " + x) : res(x),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
这只是一个草图 – 必须投入大量精力才能实现正确的解决方案.但这是我猜的概念证明. compk / compkn非常简单,因为它们不必对抗状态.
那么复杂的ES6承诺优于延续传递风格和相应的DSL(例如continuation functor / monad)有什么优势呢?
例如.我看到人们做我称之为callback lite的事情:
let foo = () => Promise.resolve().then(() => console.log('foo'));
let bar = () => Promise.resolve().then(() => console.log('bar'));
foo().then(bar);
这是一种方法,但不是唯一的方法,我个人不喜欢它,就像我不喜欢用英语或动作列表替换JavaScript的任何尝试一样.
对我而言,承诺的好处是我们可以完全避免传统回调的间接,并按顺序编写代码.箭头功能有助于:
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(await Promise.resolve('foo'));
console.log(await Promise.resolve('bar'));
})();