2016-03-03 126 views
6

我想處理序列中的許多承諾。我有一個working piece of code下面,但我想知道如果我已經複雜的承諾鏈接。我似乎正在創造大量新的關閉,我正在摸索着我的腦袋,想知道我是否錯過了一些東西。Javascript承諾序列

有沒有寫這個功能更好的辦法:

'use strict'; 
addElement("first") 
.then(x => {return addElement("second")}) 
.then(x => { return addElement("third")}) 
.then(x => { return addElement("fourth")}) 

function addElement(elementText){ 
    var myPromise = new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
return myPromise; 
} 
+3

您的箭頭功能可以簡化 - '。於是(X =>的addElement(「第二」))' - 同樣,你可以使用箭頭功能'addElement' - 但我不知道爲什麼你認爲你正在創建「大量新的關閉」 –

+0

我也遇到了這個問題,並最終使用'bind'來代替,儘管它感覺就像凌亂(但避免了額外的函數包裝):'.then( addElement.bind(null,「second」))'等等。 –

+0

想知道是否有在這裏創建的冗餘承諾對象。如果你有3個就足夠了,就像你創造6個承諾對象一樣?那麼已經創建了一個你不能重複使用的Promise對象?讓我想想我可能是錯的。 – Nishant

回答

3

你的代碼看起來接近你可以在這裏最好的。承諾可能是一個習慣的奇怪結構,尤其是當寫入promis-ified代碼通常最終會將函數嵌入到另一個函數中時。正如你可以看到here,這是一個非常常用的措辭。只有兩種可能的風格變化。首先,myPromise是不必要的,僅用於添加令人困惑的額外的代碼行。直接返回承諾更簡單。其次,你可以使用函數綁定在開始時簡化你的調用。它可能不在函數本身內部,但它確實消除了幾個閉包。這兩種變化如下圖所示:

'use strict'; 
addElement("first") 
.then(addElement.bind(null,"second")) 
.then(addElement.bind(null,"third")) 
.then(addElement.bind(null,"fourth")) 

function addElement(elementText){ 
    return new Promise(function(resolve,reject){ 
     setTimeout(function(){ 
      var element=document.createElement('H1'); 
      element.innerText = `${elementText} ${Date.now()}`; 
      document.body.appendChild(element); 
      resolve(); 
     }, Math.random() * 2000); 
    }); 
} 

值得指出的是,如果你願意重組了一下,稍微有吸引力的設計將採取的形式:

'use strict'; 
var myWait = waitRand.bind(null,2000); 
myWait 
    .then(addElement.bind(null, "first")) 
    .then(myWait) 
    .then(addElement.bind(null, "second")) 
    .then(myWait) 
    .then(addElement.bind(null, "third")) 

function waitRand(millis) { 
    return new Promise((resolve, reject) => { 
    setTimeout(resolve, Math.random() * millis); 
    } 
} 

function addElement(elementText) { 
    var element = document.createElement('h1'); 
    element.innerText = `${elementText} ${Date.now()}`; 
    document.body.appendChild(element); 
} 

此交易承諾鏈的長度爲了清楚起見,以及嵌套級別略低。

6

@TheToolBox對你有一個很好的答案。

只是爲了好玩,我將向您展示一種替代技術,使用發電機從coroutines獲得靈感。

Promise.prototype.bind = Promise.prototype.then; 

const coro = g => { 
    const next = x => { 
    let {done, value} = g.next(x); 
    return done ? value : value.bind(next); 
    } 
    return next(); 
} 

利用這一點,你的代碼將看起來像這樣

const addElement = elementText => 
    new Promise(resolve => { 
    setTimeout(() => { 
     var element = document.createElement('H1'); 
     element.innerText = `${elementText} ${Date.now()}`; 
     document.body.appendChild(element); 
     resolve(); 
    }, Math.random() * 2000); 
    }); 

coro(function*() { 
    yield addElement('first'); 
    yield addElement('second'); 
    yield addElement('third'); 
    yield addElement('fourth'); 
}()); 

有一些很有趣的東西,你可以使用發電機,承諾做。它們在這裏並不是立即可見的,因爲您的承諾不能解決任何實際值。


如果你真的resolve一些值,你可以不喜歡

// sync 
const appendChild = (x,y) => x.appendChild(y); 

// sync 
const createH1 = text => { 
    var elem = document.createElement('h1'); 
    elem.innerText = `${text} ${Date.now()}`; 
    return elem; 
}; 

// async 
const delay = f => 
    new Promise(resolve => { 
    setTimeout(() => resolve(f()), Math.random() * 2000); 
    }); 

// create generator; this time it has a name and accepts an argument 
// mix and match sync/async as needed 
function* renderHeadings(target) { 
    appendChild(target, yield delay(() => createH1('first'))); 
    appendChild(target, yield delay(() => createH1('second'))); 
    appendChild(target, yield delay(() => createH1('third'))); 
    appendChild(target, yield delay(() => createH1('fourth'))); 
} 

// run the generator; set target to document.body 
coro(renderHeadings(document.body)); 

值得注意createH1appendChild是同步的功能。這種方法有效地允許你將正常功能鏈接在一起,模糊同步和異步之間的界限。它也執行/行爲完全像您最初發布的代碼。

所以是的,這最後一個代碼示例可能會稍微有趣。


最後,

一個明顯的優點協程有過.then鏈,是所有解決承諾可以在同一範圍內進行訪問。

比較.then鏈...

op1() 
    .then(x => op2(x)) 
    .then(y => op3(y)) // cannot read x here 
    .then(z => lastOp(z)) // cannot read x or y here 

到協程...

function*() { 
    let x = yield op1(); // can read x 
    let y = yield op2(); // can read x and y here 
    let z = yield op3(); // can read x, y, and z here 
    lastOp([x,y,z]);  // use all 3 values ! 
} 

當然也有workarounds此使用的承諾,但是男孩哦它得到醜陋快...


如果您有興趣以這種方式使用發電機,我強烈建議您結賬co項目。

而這裏有一篇文章,Callbacks vs Coroutines,來自的創建者co@tj

無論如何,我希望你有樂趣學習一些其他技術^__^

3

你可以通過使addElement()返回一個函數代替它可以直接插入.then()處理程序不簡化使用的功能必須創建匿名功能:

'use strict'; 
addElement("first")() 
    .then(addElement("second")) 
    .then(addElement("third")) 
    .then(addElement("fourth")) 

function addElement(elementText){ 
    return function() { 
     return new Promise(function(resolve){ 
      setTimeout(function(){ 
       var element=document.createElement('H1'); 
       element.innerText = `${elementText} ${Date.now()}`; 
       document.body.appendChild(element); 
       resolve(); 
      }, Math.random() * 2000); 
     }); 
    } 
} 
1

關於關閉次數方面沒有太多的工作要做。函數嵌套只是你習慣於js的東西,而且問題中的代碼真的不是那麼糟糕。

正如其他人所說,編寫addElement()函數返回函數是一個更整潔的主要承諾鏈。

稍微進一步,您可能會考慮使用內部承諾鏈編寫返回的函數,從而允許從DOM元素插入中輕微地分離承諾解析。這不會造成更多和更少的關閉,但在語法上更加整潔,尤其是允許您編寫setTimeout(resolve, Math.random() * 2000);

'use strict'; 
addElement("first") 
.then(addElement("second")) 
.then(addElement("third")) 
.then(addElement("fourth")); 

function addElement(elementText) { 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      var element = document.createElement('H1'); 
      document.body.appendChild(element); 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

也許這只是我,但我覺得這更令人高興的眼睛,儘管額外的。然後(成本),因此額外的承諾,每addElement()

注意:如果您需要用值來解決承諾,您仍然有機會通過從鏈接的回調中返回值來實現此目的。

變本加厲,如果你想插入的元素出現在要求秩序,不承諾解​​決確定的順序,那麼你就可以創建/同步插入元素,並異步填充他們:

function addElement(elementText) { 
    var element = document.createElement('H1'); 
    document.body.appendChild(element); 
    return function() { 
     return new Promise(function(resolve, reject) { 
      setTimeout(resolve, Math.random() * 2000); 
     }).then(function() { 
      element.innerText = `${elementText} ${Date.now()}`; 
     }); 
    }; 
} 

所有這一切都需要在addElement()內移動兩行,以改變插入的時間,同時將element.innerText = ...行保留在原來的位置。無論您是否選擇內部承諾鏈,這都是可能的。

+0

你第一次調用addElement()需要另一個'()'來調用內部函數(如我的答案所示)。而且,您不需要額外的承諾就可以按要求的順序插入項目。這已經由其他解決方案完成。內部功能已按要求的順序調用。 – jfriend00

3

我不知道爲什麼別人留下了一個簡單的出路,你可以簡單地使用數組和reduce方法

let promise, inputArray = ['first', 'second', 'third', 'fourth']; 

promise = inputArray.reduce((p, element) => p.then(() => addElement(element)), Promise.resolve()); 
+1

你不需要分配'p = p.then(...' –

0

我寫了兩個方法在這裏:

Sequence = { 
    all(steps) { 
     var promise = Promise.resolve(), 
      results = []; 

     const then = i => { 
      promise = promise.then(() => { 
       return steps[ i ]().then(value => { 
        results[ i ] = value; 
       }); 
      }); 
     }; 

     steps.forEach((step, i) => { 
      then(i); 
     }); 

     return promise.then(() => Promise.resolve(results)); 
    }, 
    race(steps) { 
     return new Promise((resolve, reject) => { 
      var promise = Promise.reject(); 

      const c = i => { 
       promise = promise.then(value => { 
        resolve(value); 
       }).catch(() => { 
        return steps[ i ](); 
       }); 
      }; 

      steps.forEach((step, i) => { 
       c(i); 
      }); 

      promise.catch(() => { 
       reject(); 
      }); 
     }); 
    } 
}; 

Sequence.all會按順序運行函數,直到解析完參數中的所有承諾。然後返回另一個帶有參數的Promise對象,作爲一個數組,依次填充所有已解析的值。

Sequence.all([() => { 
    return Promise.resolve('a'); 
},() => { 
    return Promise.resolve('b'); 
} ]).then(values => { 
    console.log(values); // output [ 'a', 'b' ] 
}); 

Sequence.race將按順序運行函數,並在解析一個promise對象時停止運行。

Sequence.race([() => { 
    return Promise.reject('a'); 
},() => { 
    return Promise.resolve('b'); 
},() => { 
    return Promise.resolve('c'); 
} ]).then(values => { 
    console.log(values); // output [ 'a' ] 
});