2015-12-30 34 views
4

想象一下,你有一個異步的過程,是n個異步步驟的級聯:爲什麼這個map減少Promises數組不起作用,但只是減少它呢?

function step1(item){ 
    return new Promise((resolve, reject)=>{ 
    setTimeout(()=>{ 
     resolve('step 1 fort item ' + item); 
    }); 
    }); 
} 

function step2(item){ 
    return new Promise((resolve, reject)=>{ 
    setTimeout(()=>{ 
     resolve('step 2 for item ' + item); 
    },1000); 
    }); 
} 

function step3(item){ 
    return new Promise((resolve, reject)=>{ 
    setTimeout(()=>{ 
     resolve('step 3 for item ' + item); 
    },1000); 
    }); 
} 

function processItem(item){ 
    let steps = [step1, step2, step3]; 
    return steps.reduce((current, next) => { 
    return current.then(res => { 
     console.log(res); 
     return next(item); 
    }).then(res => { 
     console.log(res); 
    }); 
    },Promise.resolve()); 
} 

所以,現在你有一個項目數組,並且要處理應用功能processItem他們每個人的所有項目。但是爲了限制,所有的流程必須按順序執行,一個流程在前一個流程完成時開始。

好,如果我實現它以這樣的方式

let items = [1, 2, 3]; 
items.map(i => processItem(i)).reduce((p, next) => { 
    return p.then(() => { 
    return next; 
    }); 
}).then(() => { 
    // all finished 
}); 

你得到這樣的輸出:

step 1 for item 1 
step 1 for item 2 
step 1 for item 3 
step 2 for item 1 
step 2 for item 2 
step 2 for item 3 
step 3 for item 1 
step 3 for item 2 
step 3 for item 3 

,只需要3秒,沒有9預期。

同時,如果你實現它避免了地圖的步驟,你會得到預期的結果:

let items = [1, 2, 3]; 
items.reduce((promise, nextItem) => { 
    return promise.then(() => { 
    return processItem(nextItem); 
    }); 
}, Promise.resolve()).then(() => { 
    // all finished 
}); 

結果:

step 1 for item 1 
step 2 for item 1 
step 3 for item 1 
step 1 for item 2 
step 2 for item 2 
step 3 for item 2 
step 1 for item 3 
step 2 for item 3 
step 3 for item 3 

,並採取9秒的預期。

爲什麼會發生這種情況?我認爲,當你將一個promise返回函數映射到一個數組時,你會得到一個promise數組,因此,reduce匿名函數的第一個和第二個參數就是承諾,你可以像我在上面的第一個例子中那樣行事。我有點困惑。

+3

在你的'.map'例子中,你會立即調用'processItem'每個'i'無需等待每完成和分配方法的返回值到_Array_中的項目,所以是的,你有一個_Array_的_Promises_,但是這些promise中的每一個都沒有鏈接,在'.reduce'的例子中,你設置了一個單鏈_Promises_ –

+0

@PaulS。那麼,如果不立即調用processItem函數,映射到每個數組項的最佳方法是什麼? – jgldev

回答

3

.map被調用功能傳遞給它,如果你不想processItem在此步驟中被調用做什麼,你需要用它來重新包裝,即

let items = [1, 2, 3]; 
items.map(i =>() => processItem(i)).reduce(
    (p, next) => p.then(next), 
    Promise.resolve() 
).then(() => { 
    // all finished 
}); 
+1

或者只是讓它成爲'(p,next)=> p.then(next)' – Bergi

+0

@Bergi我複製了它(並且只是添加了括號),但是它確實很好地簡化了它 –

0

謝謝@保羅小號。

以你的答案爲基礎,我想我更喜歡來包裝原processItem功能:

function processItem(item){ 
    return function(){ 
    let steps = [step1, step2, step3]; 
    return steps.reduce((current, next) => { 
     return current.then(res => { 
     console.log(res); 
     return next(item); 
     }).then(res => { 
     console.log(res); 
     }); 
    },Promise.resolve()); 
    } 
} 

然後是map.reduce保持清潔:

items = [1, 2, 3]; 
items.map(i => processItem(i)).reduce((p, next) => { 
    return p.then(() => { 
     return next(); 
    }); 
}, Promise.resolve()).then(() => { 
    // all finished 
}); 
+0

您不應該調用該函數' processItem'不過,而是使用類似'makeItemProcessor'的東西。 – Bergi

+0

你說得對。事實上,該功能是一個Thunk,所以它就像一個動作創建者。 – jgldev