# 用异步思想手写代码实现某些场景

# 实现一个sleep函数

每隔一秒输出1,2,3,4,5

// Promise
function sleep(interval) {
    return new Promise(resolve => {
        setTimeout(resolve,interval)
    })
}
sleep(1000).then(() => {
    console.log(1)
})

async function one2fiveInAsync() {
    for(let i=1;i<=5;i++) {
        console.log(i)
        await sleep(1000)
    }
}
one2fiveInAsync()

// Generator
function *sleep(time) {
    yield new Promise((resolve,reject) {
        setTimeout(resolve,time)
    })
}
sleepGenerator(1000).next().value.then(()=>{console.log(1)})

// ES5
function sleep(callback,time) {
    if(typeof callback === 'function') {
        setTimeout(callback,time)
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 用Promise实现Ajax例子

var getJson = function(url) {
    var promise = new Promise((resolve, reject) => {
        var client = new XMLHttpRequest;
        client.open('GET', url)
        client.onreadystatechange = handler;
        client.responseType = 'json'
        client.setRequestHeader('Accept', 'application/json')
        client.send()
    })
    function handler() {
        if(this.readState !== 4) {
            return;
        }
        if(this.status === 200) {
            console.log(this.response)
            resolve(this.response)
        } else {
            reject(new Error(this.statusText))
        }
    }
    return promise
}

getJson('http://ueclub.kingdee.com/kux/index/top').then((json) => {
    console.log(json)
}, (error) => {
    console.log(error)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 实现红绿灯任务控制

红灯3s亮一次,绿灯1s亮一次,黄灯2s亮一次。如何让3个灯不断交替重复地亮呢?

function red() {
    console.log('红灯亮')
} 

function yellow() {
    console.log('黄灯亮')
}
function green() {
    console.log('绿灯亮')
}

const task = (timer,light) => {
    return new Promise((resolve,reject) => {
        setTimeout(() => {
            if(light === 'red') {
                red()
            } else if(light === 'green') {
                green()
            }  else if(light === 'yellow') {
                yellow()
            }
            resolve()
        },timer)
    })
}

const stepLight = () => {
    task(3000,'red')
        .then(() => task(1000,'green'))
        .then(() => task(2000,'yellow'))
        .then(stepLight) // 递归实现红绿灯重复执行
}
stepLight()

// async...await
const stepLight = async () => {
    await task(3000,'red')
    await task(1000, 'green')
    await task(2000, 'yellow')
    stepLight()
}
stepLight()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 请求图片进行预先加载

先实现一个请求图片的方法

const loadImg = urlId => {
    const url = `https://www.image.com/${urlId}`
    return new Promise((resolve,reject) => {
        const img = new Image()
        img.onerror = function() {
            reject(urlId)
        }
        img.onload = function() {
            resolve(urlId)
        }
        img.src = url
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

根据图片urlId依次请求图片,代码

const urlIds = [1,2,3,4,5] 
urlIds.reduce((prevPromise,urlId) => {
    return prevPromise.then(() => loadImg(urlId))
},Promise.resolve())
1
2
3
4

面向过程

const loadImgOneByOne = index => {
    const length = urlIds.length
    loadImg(urlIds[index]).then(() => {
        if(index === length -1) {
            return
        } else {
            loadImgOneByOne(++index)
        }
    })
}
loadImgOneByOne(0)
1
2
3
4
5
6
7
8
9
10
11

async...await

const loadImgOneByOne = async () => {
    for(let i of urlIds) {
        await loadImg(urlIds[i])
    }
}
loadImgOneByOne()
1
2
3
4
5
6
  • 想要提升效率将所有图片的请求一次性发出
const urlIds = [1,2,3,4,5]
const promiseArray = urlIds.map(urlId => loadImg(urlId))
Promise.all(promiseArray)
    .then(() => {
        console.log('finish load all')
    })
    .catch(() => {
        console.log('promise all catch')
    })
1
2
3
4
5
6
7
8
9
  • 控制最大并发数为3,最多一起发3个请求,剩下2个一起发出
const loadByLimit = (urlIds,loadImg,limit) => {
    const urlIdsCopy = [...urlIds]
    if(urlIdsCopy.length <= limit) {
        //如果数组长度小于最小并发数,则直接发出请求
        const promiseArray = urlIds.map(urlId => loadImg(urlId))
        return Promise.all(promiseArray)
    }
    
    const promiseArray = urlIdsCopy.splice(0,limit).map(urlId => loadImg(urlId))

    urlIdsCopy.reduce(
        (prevPromise,urlId) =>
        prevPromise
            .then(() => Promise.race(promiseArray))
            .catch(error => {console.log(error)})
            .then(resolvedId => {
                // 将resolvedId从promiseArray中删除
                let resolvedIdPostion = promiseArray.findIndex(id => resolvedId === id)
                promiseArray.splice(resolvedIdPostion,1)
                promiseArray.push(loadImg(urlId))
            })
        ,
        Promise.resolve()
    )
    .then(() => Promise.all(promiseArray))
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 实现一个lazyman

实现如下调用,lazyMan('xxx').sleep(1000).eat('333').sleepFirst(2000) sleepFirst 最先执行。

class lazyManGenerator {
    constructor(name) {
        this.taskArray = []

        //初始化任务
        const task = () => {
            console.log(`Hi,This is ${name}`)
            this.next() // 执行下一个任务
        }

        //将初始任务放入任务队列中
        this.taskArray.push(task)
        setTimeout(() => {
            this.next()
        },0)
    }
    next() {
        const nextTask = this.taskArray.shift()
        nextTask&&nextTask()
    }
    sleep(time) {
        this.sleepTask(time,false)
        return this //链式调用
    }
    sleepFirst(time) {
        this.sleepTask(time,true)
        return this
    }
    sleepTask(time,prior) {
        const task = () => {
            setTimeout(() => {
                console.log(`Wake up after ${time}`)
                this.next()
            },time * 1000)
        }
        if(prior) {
            this.taskArray.unshift(task)
        } else {
            this.taskArray.push(task)
        }
    }
    eat(name) {
        const task = () => {
            console.log(`eat ${name}`)
            this.next()
        }
        this.taskArray.push(task)
        return this
    }
}

function lazyMan(name) {
    return new lazyManGenerator(name)
}

lazyMan('Hank').sleep(10).eat('dinner')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

# 任务队列

任务队列可不断的添加异步任务(异步任务都是Promise),但只能同时处理5个任务,5个一组执行完成后才能执行下一组,任务队列为空时暂停执行,当有新任务加入则自动执行。

class RunQune{
    constructor(){
        this.list = []; // 任务队列
        this.target = 5; // 并发数量
        this.flag = false; // 任务执行状态
        this.time = Date.now()
    }
    async sleep(time){
        return new Promise(res=>setTimeout(res,time))
    }
    // 执行任务
    async run(){
        while(this.list.length>0){
            this.flag = true;
            let runList = this.list.splice(0,this.target);
            this.time = Date.now()
            await this.runItem(runList)
            await this.sleep(300) // 模拟执行时间
        }
        this.flag = false;
    }
    async runItem(list){
        return new Promise((res)=>{
            while(list.length>0){
                const fn = list.shift();
                fn().then().finally(()=>{
                    if(list.length === 0){
                        res()
                    }
                })
            }
        })
    }
    // 添加任务
    push(task){
        this.list.push(...task);
        !this.flag && this.run()
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 按期望id打印值

只能修改start函数

function start(id) {
    execute(id)
}
for (let i = 0; i < 5; i++) {
    start(i);
}
function sleep() {
    const duration = Math.floor(Math.random() * 500);
    return new Promise(resolve => setTimeout(resolve, duration));
}
function execute(id) {
    return sleep().then(() => {
        console.log("id", id);
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

TIP

id 的打印是个异步事件,在 setTimeout 回调执行,按照上面的代码,谁的倒计时先结束,id就先打印,那么想要id按顺序打印,就需要将多个异步事件同步执行,promise 的链式调用可以派上用场。

function start(id) {
    // execute(id)
    // 第一种:promise 链式调用,execute 函数返回的就是 promise ,所以可以利用这一点,通过 promise.then 依次执行下一个打印
    this.promise = this.promise ? this.promise.then(()=>execute(id)) : execute(id)

    // 第二种:先用数组存储异步函数,利用事件循环的下一个阶段,即 setTimeout 的回调函数中执行 promise 的链式调用,这方法本质上和第一种是一样的
    this.list = this.list ? this.list : []
    this.list.push(() => execute(id))
    this.t;
    if (this.t) clearTimeout(this.t)
    this.t = setTimeout(() => {
        this.list.reduce((re, fn) => re.then(() => fn()), Promise.resolve())
    })

    // 第三种:数组存储id的值,在通过 await 异步执行 execute 函数
    this.list = this.list ? this.list : []
    this.list.push(id)
    clearTimeout(this.t)
    this.t = setTimeout(async () => {
        let _id = this.list.shift()
        while (_id !== undefined) {
            await execute(_id);
            _id = this.list.shift()
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26