異步任務分爲兩大類:並行與串行。並行任務相對簡單,串行任務則有多種變體。html
並行的異步任務本質上是這樣一個問題:單個異步任務僅可能返回兩種狀態:正常、異常。假設有 m(m > 1)個併發執行的異步任務合成一個異步任務集合,那麼這個集合(也是一個異步任務)應該返回什麼?前端
Promise.all
Promise.race
Promise.settle
Promise.any
Promise.some
序列化異步問題是說:有些異步任務不能同時執行(互斥關係),必須等上一個執行完,才能執行下一個。來看幾個典型場景:webpack
好比用戶能夠勾選任意多個文件並下載,假設咱們的策略是下載完第一個再下載第二個,這種任務應該怎麼實現呢?git
能夠藉助某種隊列或循環機制,好比經過 reduce 或 for of 來實現:github
寫法一: web
寫法二: npm
寫法三: redux
市面上也有一些現成的庫能夠處理這種問題,好比 async.series、deferred-queue、promise-sequence、co 等。api
假設每一個接口請求發起時都會展現 loading,請求結束隱藏 loading。接口請求可能有不少,但每時每刻界面上只能有一個 loading。好比 a 請求發出,展現 loading,以後 b 請求發出,若是 a 請求結束時,b 尚未結束,那麼繼續展現 loading,反之則隱藏 loading,這怎麼實現呢?promise
能夠考慮一種引用計數的策略:
var loading = {
count: 0,
el: document.createTextNode('loading'),
start () {
if (this.count === 0) {
document.body.appendChild(this.el)
}
this.count += 1
},
stop () {
this.count -= 1
if (this.count === 0) {
document.body.removeChild(this.el)
}
}
}
複製代碼
競態問題是指同一類請求,前後發送,以哪個的返回爲準?好比用戶搜索 A 類電影,因爲接口遲遲未返回,用戶選擇搜索 B 類電影,若是 B 的請求尚未返回,A 卻返回了,這時怎麼辦?
每一個操做都只是單個異步任務,而不是一個序列任務,但用戶的屢次操做卻構成了一個序列任務。
顯然只有最新的請求才應該被使用,咱們能夠用時間戳來標識每一個請求。
const map = {
'fetchMovie': 0
}
function fetchMovie () {
const stamp = Date.now()
map.fetchMovie = stamp
fetch(url, params).then(res => {
if (stamp < map.fetchMovie) return null // 該請求已過期
return res
})
}
複製代碼
不過這種方案侵入性比較強,能不能實現一個相似 redux-saga 中的 takeLatest 的方法呢?takeLatest 的基本思路是:只要有最新的請求,就將以前的請求 cancel 掉,但 promise 沒有辦法 cancel(I know bluebird),這怎麼辦呢?
// 模擬一個在 t 時間後返回結果的接口請求
function request (t) {
return new Promise(resolve => {
setTimeout(function () {
resolve(t)
}, t)
})
}
const map = {}
function takeLatest (key, fn) {
if (!map[key]) {
map[key] = 1
}
return function () {
let resolve
let reject
// 嘿嘿
const a = new Promise((_res, _rej) => {
resolve = _res
reject = _rej
})
const t = Date.now()
map[key] = t
fn.apply(null, arguments).then(res => {
if (t < map[key]) return
resolve(res)
}).catch(error => {
if (t < map[key]) return
reject(error)
})
return a
}
}
// 測試
const f1 = takeLatest('fetchMovie', request)
const f2 = takeLatest('fetchOther', request)
f1(3000).then(res => {
console.log(res)
})
f2(3050).then(res => {
console.log(res)
})
setTimeout(() => {
f1(1000).then(res => {
console.log(res)
})
f2(1050).then(res => {
console.log(res)
})
}, 1000)
// 返回 setTimeout 裏的兩個「最新」的請求結果:1000,1050
複製代碼
webpack 本來只是一個打包工具,後來逐步演化成前端構建工具,構建的核心是構建過程管理,或者說構建任務管理。任務有同步、有異步,過程有並行、有串行,看上去很複雜,可是 webpack 經過 tapable 這個庫對各種同步、異步場景作了很好的抽象,tapable 提供了一系列被稱爲 hook 的 api 來處理各種同步、異步的場景。
{
AsyncParallelHook,// 異步並行任務
AsyncSeriesHook, // 異步序列任務
AsyncSeriesBailHook, // 可中斷的異步序列任務
AsyncSeriesWaterfallHook // 可傳參的異步序列任務
...
}
複製代碼
這篇文章 講得很清楚,推薦。