# 用异步思想手写代码实现某些场景
# 实现一个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
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
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
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
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
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
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
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
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
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
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
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
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
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