同步的迭代器模式
衆所周知,迭代器模式就是在已有的 對象裏 增加一個 迭代函數,然後 通過迭代函數 來執行 對應的函數
const obj = {
arr1: [1,2,3,4,5],
arr2: [1,2,3,4,5],
next(fn) {
const arr = [...this.arr1, ...this.arr2]
for (val of arr) {
fn(val)
}
}
}
obj.next(console.log)
異步的迭代器模式
先講講 不使用es6 的情況下的做法
// 這裏是被異步調用的函數
function promise1(next, value) {
setTimeout(() => {
console.log(1, value)
next(2)
}, 100);
}
function promise2(next, value) {
setTimeout(() => {
console.log(2, value)
next(3)
}, 100);
}
var events = [promise1, promise2]
做法一
function eventLoop(events, initv) {
let index = 0;
function next(value) {
if (index >= events.length) {
return value
} else {
const fn = events[index]
fn(next, value)
}
index ++
}
next(initv)
}
eventLoop(events, 1)
這裏是基於 約定 的方式 來進行的,也就是說,你異步執行完成以後,一定要執行 傳過來的 next 函數,否則 進行不下去,是不是很熟悉?
沒錯了,vue-router 和 koa2 的 use 都是基於這種寫法的
做法二
這個就是 很有名的 redux compose 中的做法 了,實際上 這個也是基於 約定的方式 來實現的 ,不過要注意的是,如果沒有那些 約定的話,這個不是 異步的,這裏就不深入講了,要不然光光這個就可以寫一兩篇單獨的博客了
function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
// 每個函數的返回值 都會被 當做 一個
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
然後看一個最簡單的中間件 redux-thunk
// 整個過程就是 柯里化 的過程,巧妙地運用了閉包的 概念
function createThunkMiddleware(extraArgument) {
// 返回的函數 其實 是一個 三重返回的 函數
// 第一重是 用於 redux 中,收集 dispatch 和 store 的閉包
// const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 第二重 就是 用來執行 上文 中的 compose 的,注意這裏的 next ,
// 其實就是傳進來的 需要執行的下一個函數
// 第三重,就是 接受 dispatch('dosomething') 中的 dosomething 這個參數的
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
其實都是將接下來的代碼的 執行 權利 和時機交給上一個函數 判斷
那使用了 es6 之後,又會有怎樣的精彩呢?
// 注意,這裏的函數的 返回值 都是 一個 Promise
function promise1(value) {
return new Promise(resolve => {
setTimeout(() => {
console.log(1, value)
resolve(1)
}, 100);
})
}
function promise2(value) {
return new Promise(resolve => {
setTimeout(() => {
console.log(2, value)
resolve(2)
}, 100);
})
}
var events = [promise1, promise2]
做法一
既然都說了迭代器了,那當然得 出來 es6 之中的 generator 了
function * generatorf(events, val) {
for (fn of events) {
val = yield fn(val)
}
}
let g = generatorf(events, 1);
function cnext(result) {
if (result.done) return;
result.value.then((val) => {
cnext(g.next(val))
})
}
cnext(g.next())
可以看到,這裏相比於之前,執行 next 的權利被進行了 轉移,到了一個專門的函數裏面了,當然,如果想要在代碼裏面獲得 next 的控制權,也可以避免使用 cnext,而是 直接在每一個 Promise 的異步函數之中,手動的去調用 g.next()
而使用的場景,類似於我司的 用戶校驗,是否已經註冊了?註冊的信息是否完善?是否是 18歲以上?等等
這樣的話,每一個 都有額外的 判斷和流程圖 要走,自然是 將控制權放在代碼裏更 方便
做法二
關於 generator 之上,還要 es7 中的 async 和 await 來進行 進一步的封裝,這樣的話,就簡單地只需要幾行代碼就夠了,但是這樣的話,就獲得不到 函數的 next 控制權了,只能交給 函數一步一步去執行
async function generatorf(events, val) {
for (fn of events) {
val = await fn(val)
}
}
generatorf(events, 1);
做法三
做法3其實使用到了 Symbol 關鍵字
憑什麼 異步調用 還和 這東西扯上關係了?看一下 阮一峯 老師 的 es6
沒錯了,看到這裏有沒有 心頭一顫?和之前 的 generator 很像,但是完全 去除了 generator 的輔助
let g = events[Symbol.iterator](1);
function cnext(result) {
if (result.done) return;
result.value(1).then((val) => {
cnext(g.next(val))
})
}
cnext(g.next())
當然,因爲這裏的數據都被包在了 數組裏,所以看不出來,但是 如果 需要執行的 異步函數被寫在了一個 配置 JSON 裏面呢?
例如:
const events = {
steps1: {
fn: promise1
},
steps2: {
fn: promise2
},
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
if (index < entries.length) {
const value = entries[index][1].fn
index ++
return {
value,
done: false
};
} else {
return { value: undefined, done: true };
}
}
};
}
}
async function gen (events, val) {
for (const iterator of events) {
val = await iterator(val)
}
}
gen(events, 1)
這樣就完成了 對於配置項的 異步迭代
當然,如果你想要 在函數裏面獲得 對應的 操作的權利,那麼 就應該換一種寫法了
const events = {
steps1: {
fn: promise1
},
steps2: {
fn: promise2
}
}
function * gen (events, val) {
for (const iterator in events) {
console.log(iterator)
val = yield events[iterator].fn(val)
// val = await iterator(val)
}
}
var g = gen(events, 1)
然後異步執行 的函數 應該這樣寫,就好像 上面 不適用 es6 的時候一樣,需要手動去執行是否next
// 注意,這裏的函數的 都需要去執行 一個 g.next 來決定 是否繼續往下走
function promise1(value) {
setTimeout(() => {
console.log(1, value)
if (true) g.next()
else // 做其他的操作
}, 100);
}
function promise2(value) {
setTimeout(() => {
console.log(1, value)
if (true) g.next()
else // 做其他的操作
}, 100)
}