4

ES6諾言

ES6承諾是有限狀態機,並因此需要複雜的實現。除此之外無極/ A +規格配備了很多粗糙的邊緣:Promises優於CPS和Continuation Functor/Monad有什麼優勢?

  • 重載then(地圖/鏈)
  • 遞歸平坦化/當時能夠同化
  • 自動升降
  • 幾個用戶(多播)
  • 急於評價

組播分發和渴望的評價是除其他外ES6承諾無法取消的原因。另外,我們不能添加我們自己的具有特定功能的圖層,因爲它們通過遞歸拼合立即被同化。

我很確定這些設計決定有很多很好的理由。然而,現在我們擁有一個不變的核心語言特性,而不是具體的競爭DSL用於用戶空間中的異步控制流。當然,互操作非常重要,但是在不考慮整個語言的後向兼容性的情況下,發展異步控制流功能的能力也是非常重要的。

延續傳遞風格

延續傳遞風格摘要從異步控制流,因爲它擺脫了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

這種方法完全缺乏異常處理。讓我們天真地適應常見的回調格局:

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

這只是一個小品 - 很多的努力將不得不投入,實現了妥善的解決辦法。但這是我猜想的一個概念證明。 compk/compkn是非常簡單的,因爲他們不必對抗狀態。

那麼複雜的ES6承諾相對於延續傳遞函數和相應的DSL(如continuation functor/monad)有什麼優勢?

+3

一個好處就是許諾相當接近於同步程序的風格,大多數人都習慣了,而你有上面的東西看起來完全陌生的人缺乏FP相當廣泛的背景。例如,我不知道你的例子是否包含一個人們想異步執行的事例。我只看到一堆以陌生方式使用的奇怪命名的函數。它看起來像你試圖解釋CFP,但你的方式,將毫無意義的人是不熟悉的人這樣做。 – JLRishe

+0

另外,我認爲這是不正確的:「此外,我們不能添加當時ABLES具有特定功能的我們自己的層,因爲它們立即被遞歸壓扁同化」 _ _這是完全可以保證的承諾你在您的代碼中使用包裹在您的諾言中,以利用額外功能(如藍鳥中的功能)。 – JLRishe

+1

'then'函數實際上是組合的。它返回一個新的承諾,並承諾連鎖如果回調是一個承諾本身並立即解決,如果它返回一個值或拋出一個異常/拒絕。這使得代碼更平坦,你確實可以使promiseCompose,要麼傳說中的承諾,構成'then'在鏈或者是返回,將調用將產生最初的承諾,然後做同樣的鏈接初始函數的函數。國際海事組織除了額外的工具外,它與CPS非常相似,因此我們不必製作不斷創造新承諾的代碼。 – Sylwester

回答

2

任何依賴於功能組合的方法的缺點是,慣用的JavaScript代碼序列被替換爲命名函數列表。承諾自己受此影響。

E.g.我看到人們做什麼,我叫回調精簡版

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) :

+1

謝謝,我感謝你的回答。但是,你暗示函數組合不是慣用的Javascript。我不同意。社區不習慣它。不常見的不同於不習慣。因此,將「Promises」納入語言主要是一個審美決定,爲異步控制流提供合成糖。雖然這是人類可以理解的,但它在某種程度上是限制性的(請參閱我的要點)。 – ftor

+1

當我們被迫從頭開始使用純函數和組合器時,我們不需要'async' /'await'。 'async' /'await'只是讓我們以虛假「簡單」的名義回到那個古老的命令式語言。爲什麼我會使用一種語言功能,它甚至不能在高階函數(HOF中的「await」)中正常工作? – ftor

+1

@ftor我認爲答案是,大多數人不希望被迫使用純函數和組合器。您提到的「老式命令式語言」對大多數人來說都是JavaScript;他們還沒有準備好口齒不清。喜歡它或討厭它,'async' /'await'很好地重用了現有的語言結構,這是任何語言的勝利,除非你希望語言成爲別的東西。我們必須同意不同意「慣用」。對我而言,HOF本質上是非慣用的,幾乎是根據定義。 'x + 1'是慣用的,'compk(inck)(x)'不是。 – jib