Node-JS专精11_01Promise精讲

Promise

先看代码

1
2
3
4
5
6
7
8
9
10
11
12
function 摇骰子(){
return new Promise((resolve, reject)=>{
setTimeout(() => {
const result = Math.floor(Math.random()*6+1)
resolve(result)
}, 3000);
})
}

// 摇骰子() // 此时返回的是一个 promise

摇骰子().then((res)=>{console.log(res)}) // 3秒后返回摇骰子的结果

这是一个异步任务,那么问题来了 异步任务什么时候执行?

  • 改写了一下
1
2
3
4
5
6
7
8
function 摇骰子(){
return new Promise((resolve, reject)=>{
const result = Math.floor(Math.random()*6+1)
resolve(result)
})
}

摇骰子().then((res)=>{console.log(res)}) // 会立刻返回 摇骰子的结果,但是它是什么时候开始呢?

面试中的 微任务/宏任务

看这样一段代码

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<button id="btn1">btn1</button>
<button id="btn2">btn2</button>
<script>
// Promise
function 摇骰子(){
return new Promise((resolve, reject)=>{
const result = Math.floor(Math.random()*6+1)
resolve(result)
})
}

btn1.onclick=()=>{
摇骰子().then(n=>console.log(`摇到了${n}`))
console.log("end");
// 执行结果:很明显 我们知道 摇骰子 是异步任务 所以会晚于 同步代码 console.log
/*
"end"
"摇到了1"
*/
}

btn2.onclick=()=>{
setTimeout(()=>{console.log("timeout1"),0})
摇骰子().then(n=>console.log(`摇到了${n}`))
setTimeout(()=>{console.log("timeout2"),0})
// 执行结果
// 如果我们还有其他的异步任务 比如 setTimeout 那么谁会优先呢?
// 答案是 先打出骰子的点数
/*
"摇到了1"
"timeout1"
"timeout2"
*/
}
</script>
</body>
</html>

微任务/宏任务这些前端名词的前因后果

  • 本来是没有 Promise的 在 ES6之前,ES6之后才有了 Promise这种异步
  • 以前的JS非常单纯只有两个东西
    • 当前任务 执行的代码(同步代码)
    • 延时任务 setTimeout / setInterval

ES6以前

  • 当前代码 如console.log(1)
  • 异步队列 setTimeout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log(1)
console.log(2)
setTimeout(()=>{console.log("timeout 1")},0)
setTimeout(()=>{console.log("timeout 2")},0)
console.log(3)
console.log(4)

/*
1
2
3
4
timeout 1
timeout 2
*/

两次 setTimeout 不会立刻执行,而是放入异步队列里
当前代码执行完了 就去看异步队列了

所以以前的js非常好理解,就是我有个当前要执行的任务,和有个等待执行的任务

ES6之后

前端觉得回调太蛋疼了,于是加了个 Promise

  • 宏任务 – 异步队列1(原来的异步队列 setTimeout)
  • 微任务 – 异步队列2 (Promise 使用)
  • 同步代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 伪代码
ajax.then(s1)
setTimeout(()=>{console.log("timeout")},0)
console.log(1);

/*
依然 console.log 放入同步任务
setTimeout 放入异步任务

此时 ,如果不引入其他东西 ajax.then(s1) 就显得特别智障
假设只有一个异步任务 那么实际就是 把 s1 加入异步队列1 , 然后把第二行的 setTimeout 加入异步队列1 s1之后

于是你惊讶的发现,那我要 then 干什么,有病吧? 直接 setTimeout(s1,0) 不就完了

所以他们就想了一个 让我们的 then 更有用的办法 ,在加一个 异步队列2

于是乎上面的代码
s1 放入 异步队列2
setTimeout 放入 异步队列1

当 同步代码执行完
先去读取 异步队列2 里的任务 去执行
等 异步队列2 里的任务执行完了, 再去读取 异步队列1 里的任务 去执行
*/

于是分别给 异步队列1 / 异步队列2 取了个名字叫做 宏任务 / 微任务

如果队列里有任务,先把 微任务 做完,再去做 宏任务

Promise 其他API

继续以摇骰子为例

1
2
3
4
5
6
7
8
function 摇骰子(){
return new Promise((resolve, reject)=>{
const result = Math.floor(Math.random()*6+1)
resolve(result)
})
}
// 随机的
摇骰子().then((res)=>{console.log(res)})
  • Promise.resolve(result) 制造一个成功(或失败)

    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
    // 制造成功
    // 你想造假一直 摇6

    function 摇6的骰子(){
    return new Promise((resolve, reject)=>{
    resolve(6)
    })
    }

    function 摇6的骰子2(){
    return Promise.resolve(6)
    }

    // 摇6的骰子
    // 摇6的骰子().then((res)=>{console.log(res)})
    // 摇6的骰子2().then((res)=>{console.log(res)})


    // 制造失败
    function 摇骰子返回失败(){
    return Promise.resolve(
    new Promise((resolve,reject)=>reject())
    )
    }

    摇骰子返回失败().then((res)=>{console.log(res)})
  • Promise.reject(reason) 制造一个失败

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function 摇坏的骰子(){
    return new Promise((resolve, reject)=>{
    reject('坏了')
    })
    }

    function 摇坏的骰子2(){
    return Promise.reject('坏了')

    }
    摇坏的骰子().then(null,(reason)=>console.log(reason))
    摇坏的骰子2().then(null,(reason)=>console.log(reason))
  • Promise.all(数组) 等待全部成功,或有一个失败

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 所有人都成功了,才叫成功 返回所有成功的结果
    Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]).then((res)=>{console.log(res)})
    // 打印 [1,2,3]

    // 某个失败 返回失败
    Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.reject('错了3')]).then((res)=>{console.log(res)},(err)=>{console.log(err)})
    // 打印 "错了3"

    Promise.all([Promise.resolve(1"),Promise.reject('错了2'),Promise.reject('错了3')]).then((res)=>{console.log(res)},(err)=>{console.log(err)})
    // 打印 "错了2"

    Promise.all([Promise.reject("错了1"),Promise.reject('错了2'),Promise.reject('错了3')]).then((res)=>{console.log(res)},(err)=>{console.log(err)})
    // 打印 "错了1"
    • 所以 Promise.all 非常没用,因为有一个失败就返回了不等所有的结果
    • 请使用 Promise.allSettled
  • Promise.race(数组) 等待第一个状态改变

    1
    同时发很多请求,只要又一个成功了,就成功,或者一个失败了就失败
  • Promise.allSettled(数组) 等待全部状态改变,目前处于 Stage-4(板上钉钉的实现)

    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
    Promise.allSettled([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]).then((res)=>{console.log(res)},(err)=>{console.log(err)})
    // resolve 打印
    /*
    [
    {status: "fulfilled", value: 1},
    {status: "fulfilled", value: 2},
    {status: "fulfilled", value: 3}
    ]
    */

    Promise.allSettled([Promise.resolve(1),Promise.reject('错了2'),Promise.resolve(3)]).then((res)=>{console.log(res)},(err)=>{console.log(err)})
    // reject 打印
    /*
    [
    {status: "fulfilled", value: 1},
    {status: "rejected", reason: "错了2"},
    {status: "fulfilled", value: 3}
    ]
    */

    Promise.allSettled([Promise.reject("错了1"),Promise.reject('错了2'),Promise.reject('错了3')]).then((res)=>{console.log(res)},(err)=>{console.log(err)})

    // reject 打印
    /*
    [
    {status: "rejected", reason: "错了1"},
    {status: "rejected", reason: "错了2"},
    {status: "rejected", reason: "错了3"}
    ]
    */

由于Promise.allSettled很多浏览器不支持,我们依旧无法使用

所以用 Promise.all 实现

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 假设还是三个 promise 任务  第一个和第二个会失败
task1 = () => new Promise((resolve,reject)=>{
setTimeout(()=>{
reject("第一扇门关了")
},3000)
})

task2 = () => new Promise((resolve,reject)=>{
setTimeout(()=>{
reject("第二扇门关了")
},4000)
})

task3 = () => new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve("第三扇门开了")
},5000)
})

// 如果这样
// 发现第一个失败就 结束了
Promise.all([task1(),task2(),task3()])
.then(null,(reason)=>console.log(reason))


// 正确姿势 把每个任务都 then一下 因为 Promise.then 会返回新的 promise
Promise.all([
task1().then(()=>({"status":"ok"}),()=>({status:"not ok"})),
task2().then(()=>({"status":"ok"}),()=>({status:"not ok"})),
task3().then(()=>({"status":"ok"}),()=>({status:"not ok"})),
])
.then((result)=>console.log(result))
/*
[
{status: "not ok"},
{status: "not ok"},
{status: "ok"}
]
*/

// 优化代码 因为写的太重复了
setSuccessResult = (promise) => promise.then(
(value)=>({status:"ok",value}),
(reason)=>({status:"not ok",reason}),
)
Promise.all([
setSuccessResult(task1()),
setSuccessResult(task2()),
setSuccessResult(task3()),
])
.then((result)=>console.log(result))
// 打印结果
/*
[
{status: "not ok", reason: "第一扇门关了"}
{status: "not ok", reason: "第二扇门关了"}
{status: "ok", value: "第三扇门开了"}
]
*/

// 还能再次简化
setSuccessResultList = (promiseList) => promiseList.map(
promise => promise.then(
(value)=>({status:"ok",value}),
(reason)=>({status:"not ok",reason})
)
)

Promise.all(setSuccessResultList([task1(),task2(),task3()]))
.then((result)=>console.log(result))

// 再次简化 你不是不支持 Promise.allSettled 吗?
Promise.allSettled2 = function(promiseList){
return Promise.all(setSuccessResultList(promiseList))
}

// 这其实就是 allSettled2 的实现
Promise.allSettled2([task1(),task2(),task3()])
.then((result)=>console.log(result))