前言

同步:【函数视角】函数调用之后,需要等待函数返回之后再调用下一个函数
异步:【函数视角】函数调用之后,无需等待函数返回就可以调用下一个函数
阻塞:【线程视角】任务调用之后,当前线程被挂起,需要等待被唤醒才可以继续工作
非阻塞:【线程视角】任务调用之后,当前线程不会被挂起,无需等待被唤醒也可以继续工作
串行:【CPU视角】任务之间有依赖关系,每个任务只能由1个CPU核心执行
并行:【CPU视角】任务之间没有依赖关系,多个任务由多个CPU核心执行

es5实现异步串行

难点拆解:

  1. 核心部分,串行如何实现?串行的本质降维到函数视角,就是一个函数执行完,执行下一个函数;同步的函数还好说,异步的时候需要考虑使用回调来执行下一个函数。那我们就把问题变成:如何实现执行函数之后执行回调?很自然地有两种解法:栈方法循环、递归
  2. done方法如何实现?done方法的本质是所有任务串行执行完之后再执行done方法。但是需要留意,是先执行函数实体,再返回done方法对象让用户注册回调函数。

show me the code

function log (x, cb) {
    console.log(x)
    cb && cb()
}

function sleep (v, time, cb) {
    setTimeout(() => {
        console.log(v)
        cb && cb()
    }, time)

}

function serialize (tasks) {
    let doneCb;
    function next () {
        let fn = tasks.shift()
        if (fn) {
            fn(next)
        } else {
            doneCb && doneCb()
        }
    }
    next();
    return {
        done: (cb) => {
            if (cb) {
                doneCb = cb;
            }
        }
    }
}

serialize([
    (cb) => log(1, cb),
    (cb) => log(2, cb),
    (cb) => sleep(3, 3000, cb),
    (cb) => log(4, cb)
]).done(() => {
    console.log('done')
})

思路总结:
使用递归实现next方法,实现一个任务执行完再执行下一个任务。留个思考题,如果用栈方法又该如何实现呢,还有哪些细节需要处理?

es6实现异步串行

方案一:async/await
难点分析:

  1. async/await最后会返回promise对象,那如何实现返回done对象呢?里面再实现一个async/await函数即可,外层直接返回done对象。
  2. done方法是否可以用es5方法中一样在判断fn不存在的时候执行呢?不可以,因为遇到await就跳出异步处理了,下面的函数还没有执行,所以此时是拿不到done回调的方法。

    function log (x, cb) {
     console.log(x)
    }
    
    function sleep (v, time) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log(v)
             resolve()
         }, time)
     })
    }
    
    function serialize (tasks) {
     async function consume () {
         while (tasks.length) {
             let fn = tasks.shift()
             if (fn) {
                 await fn()
             }
         }
     }
     consume();
     return {
         done: (cb) => {
             if (cb) {
                 tasks.push(cb)
             }
         }
     }
    }
    
    serialize([
     (cb) => log(1, cb),
     (cb) => log(2, cb),
     (cb) => sleep(3, 3000, cb),
     (cb) => log(4, cb)
    ]).done(() => {
     console.log('done')
    })

方案二:Promise
难点分析:

  1. 如何巧妙使用then方法实现串行?
  2. done方法又如何考虑呢?

    serialize([
     log(1),
     log(2),
     sleep(3),
     log(4)
    ]).done(() => {
     console.log('done')
    })
    
    // 输出:1 2 3 4 done
    
    function log (x) {
     return () => console.log(x)
    }
    
    function sleep (y) {
     return () => new Promise((resolve) => {
         setTimeout(() => {
             resolve(y)
         }, 0)
     })
    }
    
    function isPromise (obj) {
     return typeof obj === 'object' && typeof obj.then === 'function'
    }
    
    function serialize (arr) {
     let promiseResolver = Promise.resolve()
     arr.forEach(item => {
         let result
         promiseResolver = promiseResolver.then(() => {
             // 同步任务直接执行
             result = item(); 
             // 异步任务直接返回,并写一个回调执行console
             if (isPromise(result)) {
                 return result.then(x => {
                     console.log(x)
                 })
             }
         })
     })
    
     return {
         done: (fn) => {
             promiseResolver = promiseResolver.then(() => {fn()});
         }
     }
    }

    es5实现异步并行

    难点分析:

  3. 如何实现异步并行?其实就是所有任务都执行一遍,等最迟完成的任务完成了就调用done方法。那如何知道所有任务都执行完了?计算器/状态标记
  4. 什么时候调用done方法?那就是计数器得到任务数量,就执行。所有任务的状态标记为执行完毕,就执行。

    parallel([
     (cb) => log(1, cb),
     (cb) => sleep(2, 1000, cb),
     (cb) => log(3, cb),
     (cb) => sleep(4, 2000, cb),
     (cb) => log(5, cb)
    ]).done(() => {
     console.log('done')
    })
    
    // 输出:1 3 5 2 4 done
    // 其中done在2秒后执行
     */
    
    function log (x, cb) {
         console.log(x)
         cb && cb()
    }
    
    function sleep (v, time, cb) {
         setTimeout(() => {
             console.log(v)
             cb && cb()
         }, time)
    }
    
    function parallel (tasks) {
     let doneCb;
     let count = 0;
     let tasksLength = tasks.length;
     function isFinish() {
         console.log(count, tasksLength)
         if (count == tasksLength) {
             doneCb && doneCb()
         }
     }
     function taskCb() {
         count++;
         isFinish()
     }
     while(tasks.length) {
         let fn = tasks.shift();
         if (fn) {
             fn(taskCb);
         }
     }
     return {
         done: (cb) => {
             if (cb) {
                 doneCb = cb;
             }
         }
     }
    }
    
    parallel([
     (cb) => log(1, cb),
     (cb) => sleep(2, 1000, cb),
     (cb) => log(3, cb),
     (cb) => sleep(4, 2000, cb),
     (cb) => log(5, cb)
    ]).done(() => {
     console.log('done')
    })
    

    es6实现异步并行

    function log (x) {
     return new Promise((resolve) => {
         console.log(x)
         resolve()
     })
    }
    
    function sleep (v, time) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log(v)
             resolve()
         }, time)
     })
    }
    
    function parallel (tasks) {
     let doneCb;
     Promise.all(tasks).then(() => {
         doneCb && doneCb();
     })
     return {
         done: (cb) => {
             if (cb) {
                 doneCb = cb;
             }
         }
     }
    }
    
    parallel([
     log(1),
     sleep(2, 1000),
     log(3),
     sleep(4, 2000),
     log(5)
    ]).done(() => {
     console.log('done')
    })

    Reference

    https://cloud.tencent.com/dev...
    https://segmentfault.com/a/11...


RockerLau
363 声望11 粉丝

Rocker Lau