js 異步迭代器

同步的迭代器模式

衆所周知,迭代器模式就是在已有的 對象裏 增加一個 迭代函數,然後 通過迭代函數 來執行 對應的函數

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)
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章