共计 41551 个字符,预计需要花费 104 分钟才能阅读完成。
一 目录
不折腾的前端,和咸鱼有什么区别
目录 |
---|
一 目录 |
二 前言 |
2.1 浅思 |
2.2 参考文献 |
三 Promise 初探 |
3.1 是什么 |
3.2 为什么 |
3.3 怎么用 |
四 Promise 根底 |
4.1 new Promise |
4.2 Promise 状态 |
五 题库:根底题 |
5.1 题目一 |
5.2 题目二 |
5.3 题目三 |
5.4 题目四 |
5.5 题目五 |
5.6 题目六 |
六 题库:联合 setTimeout |
6.1 题目一 |
6.2 题目二 |
6.3 题目三 |
6.4 题目四 |
6.5 题目五 |
6.6 题目六 |
6.7 题目七 |
七 .then() 链式操作 |
7.1 两个参数 |
7.2 链式调用 |
八 .catch() 捕捉问题 |
九 .finally() 强制执行 |
十 题库:.then()、.catch()、.finally() |
10.1 题目一 |
10.2 题目二 |
10.3 题目三 |
10.4 题目四 |
10.5 题目五 |
10.6 题目六 |
10.7 题目七 |
10.8 题目八 |
10.9 题目九 |
10.10 题目十 |
10.11 题目十一 |
10.12 题目十二 |
10.13 题目十三 |
10.14 题目十四 |
十一 .all() 接力赛 |
十二 .race() 个人赛 |
十三 题库:.all()、.race() |
13.1 题目一 |
13.2 题目二 |
13.3 题目三 |
13.4 题目四 |
十四 Promise 源码 |
十五 题库:联合 async/await |
15.1 题目一 |
15.2 题目二 |
15.3 题目三 |
15.4 题目四 |
15.5 题目五 |
15.6 题目六 |
15.7 题目七 |
15.8 题目八 |
15.9 题目九 |
15.10 题目十 |
15.11 题目十一 |
十六 综合题 |
16.1 题目一 |
16.2 题目二 |
16.3 题目三 |
十七 大厂题 |
17.1 应用 Promise 实现每隔一秒输入 1、2、3 |
17.2 应用 Promise 实现红绿灯交替反复亮 |
17.3 实现 mergePromise 函数 |
17.4 依据 PromiseA+ 实现一个本人的 Promise |
17.5 封装一个异步加载图片的办法 |
17.6 限度异步操作并发数并尽可能快地实现 |
17.7 JS 实现异步调度器 |
十八 总结 |
[](url)
二 前言
返回目录
本文会联合 Promise
知识点 + 训练题的模式进行解说。
前置知识点:
- JavaScript 异步
- Event Loop
如不太理解这些知识点,请先点击返回观看,防止走火入魔。
2.1 浅思
返回目录
JavaScript 里的异步计划的演进时,是用上面这种程序:
callback -> promise -> generator -> async/await
在计算机行业,流行着一种奢侈还原论的迷思:
- 越靠近底层,技术含量越高。
- 每个程序员都有读懂底层源代码的谋求。
这在肯定水平上是正确的。
不过,咱们也应该看到,一旦底层和表层之间,造成了畛域鸿沟。精通底层,并不能代表在表层的程度。
比方游戏的开发者,不肯定是游戏中的佼佼者。这在 FPS 射击游戏或者格斗游戏里尤为显著,这些游戏里的绝大部分顶尖玩家,齐全不会写代码。
如果将精通 Promise
定义为,长于在各种异步场景中应用 Promise
解决问题。
那么,能手写 Promise
实现,对精通 Promise
帮忙不大。
来源于微信公众号:工业聚
2.2 参考文献
返回目录
请了解这是一个工作 2 年多的小前端做的整顿,可能会疏忽一些大佬文章的精髓,从而做出不失当的排序。
以下文章排名不分先后,纯正是集体比照,丝毫没有诽谤一丝心态,所有文章是带着膜拜心理一一浏览结束的。
不错的:
- [x] ES6 入门 – Promise 对象【浏览倡议:2h】
- [x] 要就来 45 道 Promise 面试题一次爽到底【浏览倡议:8h】
- [x] 面试精选之 Promise【浏览倡议:20min】
手写 Promise:
- [x] 最简实现 Promise,反对异步链式调用(20 行)【倡议浏览:20min】
- [x] BAT 前端经典面试问题:史上最最最具体的手写 Promise 教程【浏览倡议:30min】
- [x] 100 行代码实现 Promises/A+ 标准【浏览倡议:30min】
- [x] 一起学习造轮子(一):从零开始写一个合乎 Promises/A+ 标准的 promise【浏览倡议:略读】
- [x] Promise 实现原理(附源码)【浏览倡议:略读】
- [x] 分析 Promise 内部结构,一步一步实现一个残缺的、能通过所有 Test case 的 Promise 类【倡议浏览:略读】
- [x] 小邵教你玩转 promise 源码【倡议浏览:略读】
- [x] Promise 不会??看这里!!!史上最通俗易懂的 Promise!!!【倡议浏览:略读】
Promises/A+ 标准:
- [x] Promises/A+ 标准(中文版【浏览倡议:无】
- [x] Promises/A+ 标准(英文版)【浏览倡议:无】
- [x] Promises/A+ 测试仓库【浏览倡议:无】
减少经历的:
- [x] ES6 之 Promise 常见面试题【浏览倡议:10min】
- [x] Promise 必知必会(十道题)【浏览倡议:10min】
- [x] 大白话解说 Promise(一)【浏览倡议:30min】
三 Promise 初探
返回目录
3.1 是什么
返回目录
什么是 Promise
?
咱们间接打印一下:
console.dir(Promise);
/*
Promise(all: ƒ all()
arguments: (...)
caller: (...)
length: 1
name: "Promise"
prototype: Promise {constructor: ƒ, then: ƒ, catch: ƒ, finally: ƒ, Symbol(Symbol.toStringTag): "Promise"}
race: ƒ race()
reject: ƒ reject()
resolve: ƒ resolve()
Symbol(Symbol.species): (...)
Symbol(allSettled): ƒ (d)
get Symbol(Symbol.species): ƒ [Symbol.species]()
__proto__: ƒ ()
[[Scopes]]: Scopes[0]
)
*/
是的,Promise
是一个构造函数。
就这么简略,别想其余杂七杂八的,好不好用才是要害。
3.2 为什么
返回目录
为什么须要 Promise
?
看一段代码:
const action = () => {setTimeout(() => {ajax('http://www.xxx.com/getData', (text) => {if (text === 'hello') {doSomething2();
} else if (text === 'world') {doSomething3();
}
})
}, 500);
};
action();
doSomething1();
这种就是回调天堂式的代码:
- 执行
action
,将setTimeout
放进宏工作队列 - 执行
doSomething1()
,执行外面代码 - 500ms 或当前,执行
ajax
,期待实现 ajax
实现后,判断text
,进而执行doSomething2()
或者doSomething3
。
这无疑看起来是挺不容易的,因为当 doSomething
外面的代码再丰盛起来,你不认真看,还真不知道这代码会怎么走。
再看一个例子:
申请 1(function(申请后果 1){申请 2(function(申请后果 2){申请 3(function(申请后果 3){申请 4(function(申请后果 4){申请 5(function(申请后果 5){申请 6(function(申请后果 3){...})
})
})
})
})
})
有没有头皮发麻的感觉。
回调天堂带来的负面作用有以下几点:
- 代码臃肿。
- 可读性差。
- 耦合度过高,可维护性差。
- 代码复用性差。
- 容易滋生 bug。
- 只能在回调里解决异样。
所以,为了解决这种痛楚,这就须要一个承诺:
- 把某件事交给某个人,这个人做完后,不论失败还是胜利,都会给你一个回应,这样你就能安心将事件交给它了。
这个人就叫 Promise
,咱们看看它直观的作用:
function getData() {const p = new Promise((resolve, reject) => {
// Ajax 申请等
setTimeout(() => {console.log('获取数据胜利');
resolve('传入胜利后的数据');
}, 1000);
});
return p;
}
getData().then((res) => {
// 获取到数据,而后进行解决
console.log(res);
// 如果上面还有 Ajax 申请,那么持续调用
// getData2()});
这样咱们就能够安心游玩了。
3.3 怎么用
返回目录
Promise
的应用,说难也不难,咱们间接上代码:
// 1. 设置一个 promise
const promise = new Promise((resolve, reject) => {
// 2. 设置一个 0-10 随机数
const number = Math.floor(Math.random() * 10);
// 3. 如果这个数大于 5,咱们当它胜利了
if (number > 5) {resolve('大于 5'); // resolve 相当于解决的意思
} else { // 4. 否则就是失败的
reject('小于 5'); // reject 相当于解决失败的意思
}
});
// 5. 如果是 resolve,那就走 .then;如果是 reject,那就走 .catch
promise.then((res) => {console.log('胜利:', res);
}).catch((error) => {console.log('失败:', error);
}).finally(() => {
// 6. 留神 finally 就是剧终的意思,不论是好终局还是坏终局,都是终局
console.log('不论后面产生了啥,我都会调用');
});
它的输入:
输入 1
失败:小于 5
不论后面产生了啥,我都会调用
输入 2
胜利:大于 5
不论后面产生了啥,我都会调用
因为咱们用了随机值,所以下面 2 种输入都有可能。
至于它外面的 resolve
、reject
、.then()
、.catch()
、.finally()
,咱们暂且不论,前面再扯,就问问本人后面的了解了没?
如果了解了,那么祝贺你对 Promise
有了个简略的理解。
四 Promise 根底
返回目录
4.1 new Promise
返回目录
首先,咱们先看看如何走一个 new Promise
:
const promise = new Promise((resolve, reject) => {console.log(resolve); // [Function]
console.log(reject); // [Function]
});
console.log(promise); // Promise {<pending>}
而后,咱们对这几个概念进行辨别:
Promise
对象是一个构造函数,用来生成Promise
实例,所以new Promise()
有余奇怪。new Promise()
传入一个函数,这个函数能够带 2 个参数:resolve
和reject
。resolve
的作用是将Promise
对象的状态从“未实现”变为“胜利”(pending -> resolved
)reject
的作用是将Promise
对象的状态从“未实现”变为“失败”(pending -> rejected
)- 在没有执行
resolve
和reject
之前,它们还是pending
的。
最初,想必这么一说,小伙伴们对这个有一个清晰概念了。
那么上面咱们再看看 Promise
的状态。
4.2 Promise 状态
返回目录
Promise
有 3 种状态:pending
、fulfilled
、rejected
- 初始状态:
pending
- 胜利状态:
fulfilled
(理论打印会看到resolved
) - 失败状态:
rejected
如果你在 new Promise
中用了 resolve()
,那么它就会走 .then()
;
如果你用的是 reject()
,那么它就走 .catch()
。
怎么说呢?
const promise1 = new Promise((resolve, reject) => {resolve('胜利');
});
promise1.then((res) => {console.log('res 1:', res);
}).catch((error) => {console.log('error 1:', error);
})
const promise2 = new Promise((resolve, reject) => {reject('失败');
});
promise2.then((res) => {console.log('res 2:', res);
}).catch((error) => {console.log('error 2:', error);
})
在这段代码中,它的输入为:
res 1:胜利
error 2:失败
这很容易了解,因为 promise1
走了 resolve
而 promise2
走了 reject
。
Promise
的状态一经扭转就不能再进行更改。
看上面代码:
const promise = new Promise((resolve, reject) => {resolve('胜利 1');
reject('失败');
resolve('胜利 2');
});
promise.then((res) => {console.log('res:', res);
}).catch((err) => {console.log('err:', err);
});
输入啥?
没错输入的是:res:胜利 1
。
空头支票,你批准将你身边妹子介绍给我了,回头又说不行了,这怎么成。
疯狂暗示
同样,Promise
也是不容许返回的:
- 你将状态改为了
resolved
,那么就别想再改了,乖乖走.then()
吧!
OK,看到这里你对 Promise
根底有肯定理解了,咱们上题吧!
五 题库:根底题
返回目录
在这之前,强调一遍 Event Loop
。
Event Loop
执行程序:
- 一开始整个脚本
script
作为一个宏工作执行 - 执行过程中,同步代码 间接执行, 宏工作 进入宏工作队列, 微工作 进入微工作队列。
- 以后宏工作执行完出队,查看微工作列表,有则顺次执行,直到全副执行结束。
- 执行浏览器 UI 线程的渲染工作。
- 查看是否有
Web Worker
工作,有则执行。 - 执行完本轮的宏工作,回到步骤 2,顺次循环,直到宏工作和微工作队列为空。
微工作 包含:
MutationObserver
Promise.then()/catch()
- 以
Promise
为根底开发的其余技术,例如fetch API
- V8 的垃圾回收过程
- Node 独有的
process.nextTick
宏工作 包含:
script
setTimeout
setInterval
setImmediate
I/O
UI rendering
举例一段代码:
const promise = new Promise((resolve, reject) => {console.log('1');
resolve('2');
});
setTimeout(() => {console.log('3');
}, 0);
promise.then((res) => {console.log(res);
});
输入:1 -> 2 -> 3
。
怎么了解呢?
在所有工作开始的时候,因为宏工作包含了
script
,所以浏览器会先执行一个宏工作,在这个过程中你看到的提早工作(例如setTimeout
)将会被放到下一个宏工作中执行。
- 先走
script
。 - 碰到
promise = new Promise
,间接走外面。 - 打印出 1。
- 碰到
resolve()
,将Promise
状态改为resolved
,将Promise.then()
丢进script
这个宏工作下的微工作队列中。 - 此时
script
宏工作下的微工作队列有:promise.then()
。 - 碰到
setTimeout()
,将其丢进宏工作队列中。 - 此时宏工作队列有:
script
、setTimeout
。 - 同步工作执行结束。
- 查看以后宏工作
script
下的微工作,并循环出队。 - 输入
2
。 - 宏工作
script
下没有任何可执行的了,走下一个宏工作setTimeout
。 - 输入
3
。 - 宏工作队列走完了,代码执行结束。
简略来说:
- 先走
script
。 - 碰到的宏工作
setTimeout
等放前面,等script
走完再出队。 - 碰到的微工作看以后宏工作,是
script
就丢script
外面,是setTimeout
就丢setTimeout
外面。 - 每次宏工作走完同步工作和微工作,就走下一个宏工作。
- 循环步骤 4。
就这么简略,说多有益,刷题加深印象!
5.1 题目一
返回目录
const promise = new Promise((resolve, reject) => {console.log('promise');
});
console.log('1', promise);
/*
输入程序即剖析:输入:* promise
* 1 Promise {<pending>}
剖析:1. 从上到下,碰到 new Promise,输入 'promise'
2. 输入 '1' 和 promise 以后状态
*/
5.2 题目二
返回目录
const promise = new Promise((resolve, reject) => {console.log(1);
resolve('success');
console.log(2);
});
promise.then(() => {console.log(3);
})
console.log(4);
/**
输入过程及剖析:输入:1 -> 2 -> 4 -> 3
剖析:1. 从上往下,碰到 new Promise,先走 1
2. 碰到 resolve,扭转 Promise 状态
3. 输入 2
4. 碰到 .then(),将其丢进微工作
5. 输入 4
6. 回来执行步骤 4 的微工作,输入 3
*/
5.3 题目三
返回目录
const promise = new Promise((resolve, reject) => {console.log(1);
console.log(2);
});
promise.then(() => {console.log(3);
})
console.log(4);
/**
输入过程及剖析:输入:1 -> 2 -> 4
剖析:1. 从上往下,碰到 new Promise,先走 1
2. 持续往下,输入 2
3. 碰到 .then(),因为 new Promise 中并没有 resolve,所以不会把它丢进微工作
4. 输入 4
*/
5.4 题目四
返回目录
const promise1 = new Promise((resolve, reject) => {console.log('promise1');
resolve('resolve1');
})
const promise2 = promise1.then((res) => {console.log(res);
});
console.log('1', promise1);
console.log('2', promise2);
/**
输入程序及剖析:输入:* promise1
* 1 Promise {<resolve>: 'resolve1'}
* 2 Promise {<pending>}
* resolve1
剖析:1. 碰到 new Promise,输入 promise1
2. 碰到 resolve,扭转 Promise 状态,并保留后果
3. 碰到 promise1.then,放进微工作队列
4. promise2 是一个新的状态为 pending 的 Promise
5. 输入 1 和 promise1,以后 promise1 的状态为 resolve,并且存在 'resolve1'
6. 输入 2 和 promise2,以后 promise2 的状态为 peding
7. 宏工作走完,执行微工作,输入 resolve1
*/
5.5 题目五
返回目录
const fn = () => (new Promise((resolve, reject) => {console.log(1);
resolve('success');
}));
fn().then((res) => {console.log(res);
})
console.log('start');
/**
执行程序和解析:程序:* 1
* 'start'
* 'success'
解析:1. fn 是一个立刻执行函数,所以会先执行 new Promise,所以输入 1
2. 碰到 resolve,将 Promise 状态扭转
3. 碰到 .then(),因为后面扭转了状态,所以会将其放进微工作
4. 输入 'start'
5. 宏工作走完,执行微工作,输入 'success'
*/
5.6 题目六
返回目录
const fn = () => {return new Promise((resolve, reject) => {console.log(1);
resolve('success');
})
};
console.log('start');
fn().then((res) => {console.log(res);
});
/**
执行程序和解析:程序:* 'start'
* 1
* 'success'
解析:上一道题是立刻执行函数,这道题不是
所以会在 fn() 调用的时候,才会执行 new Promise
*/
六 题库:联合 setTimeout
返回目录
对于宏工作 setTimeout
,咱们就要举例一道经典题了:
for (var i = 0; i < 3; i++) {setTimeout(() => {console.log(i);
}, 0);
}
这道题的输入:
3
3
3
从 Event Loop
的角度来看:
- 走同步工作,
var i
遍历走完,i = 3
(var
变量净化)。 for()
遍历的同时,将 3 个setTimeout
塞进了宏工作中。script
这个宏工作执行结束。- 顺次执行 3 个
setTimeout
,因为此时i
为3
,所以会顺次输入 3 个 3。
当然,解决方案也很简略:
- 办法一:设置立刻执行函数
通过设置立刻执行函数的形式,造成块作用域,解决 i
的净化问题。
for (var i = 0; i < 3; i++) {(function(i) {setTimeout(() => {console.log(i);
}, 0);
})(i);
}
- 办法二:设置 let
let
会让 for
造成块作用域,从而避免净化,解决问题。
for (let i = 0; i < 3; i++) {setTimeout(() => {console.log(i);
}, 0);
}
当然,setTimeout
还有一个问题:
setTimeout(() => {console.log(1);
}, 0);
setTimeout(() => {console.log(2);
}, 1000);
setTimeout(() => {console.log(3);
}, 500);
这份代码输入啥?
1
3
2
setTimeout
接管 2 个参数:
function()
:回调函数。在第二个参数指定的工夫后执行。timer
:执行工夫(毫秒)。在n
毫秒后执行回调函数。
一般来说,宏工作遵循队列的规定,按程序入队出队,这样应该输入:1 -> 2 -> 3
。
这在 timer
雷同的状况下的确如此。
然而,在 timer
不同的状况下,须要依照 优先队列 的形式进行入队:
- 谁最小,谁在后面。
所以这里输入的程序是:1 -> 3 -> 2
。
你不须要了解 优先队列 是啥,你想着尊老爱幼,
timer
越小越在后面即可。
OK,对于 setTimeout
相干知识点 jsliang 介绍结束,咱们看题!
6.1 题目一
返回目录
console.log('start');
setTimeout(() => {console.log('time');
});
Promise.resolve().then(() => {console.log('resolve');
});
console.log('end');
/**
执行程序和剖析:程序:* 'start'
* 'end'
* 'resolve'
* 'time'
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script
3. 输入 'start'
4. 碰到 setTimeout,丢进宏工作队列
5. 碰到 Promise,而后 Promise 变成 resolve() 状态后,执行了 .then(),所以丢进微工作队列
6. 输入 'end'
7. 遍历本次的微工作队列,输入步骤 5 的内容,即 'resolve'
8. 步骤 7 走完,执行下一个宏工作队列,输入 'time'
*/
6.2 题目二
返回目录
const promise = new Promise((resolve, reject) => {console.log(1);
setTimeout(() => {console.log('timerStart');
resolve('success');
console.log('timerEnd');
}, 0);
console.log(2);
});
promise.then((res) => {console.log(res);
});
console.log(4);
/**
执行程序和剖析:程序:* 1
* 2
* 4
* 'timerStart'
* 'timerEnd'
* 'success'
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script 这个宏工作
3. 碰到 new Promise,输入 1
4. 碰到 setTimeout,放进宏工作队列
5. 输入 2
6. 碰到 .then(),然而没有钥匙(resolve),跳过
7. 输入 4
8. 以后没有微工作,执行下一个宏工作 setTimeout
9. 输入 'timerStart'
10. Promise 碰到 resolve,扭转状态,表明 .then() 能够放进微工作了
11. 输入 'timerEnd'
12. 执行宏工作 setTimeout 下的微工作,即 Promise.then()
13. 输入 'success'
*/
6.3 题目三
返回目录
setTimeout(() => {console.log('timer1');
setTimeout(() => {console.log('timer3');
}, 0);
}, 0);
setTimeout(() => {console.log('timer2');
}, 0);
console.log('start');
/**
执行程序和剖析:程序:* 'start'
* 'timer1'
* 'timer2'
* 'timer3'
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script 这个宏工作
3. 碰到第一个 setTimeout,丢进宏工作队列
4. 这里的 setTimeout 为输入 'timer1' 的一整个 setTimeout,此时并不会进入外面执行代码
5. 碰到第二个 setTimeout,丢进宏工作队列
6. 输入 'start'
7. 查看微工作,并没有微工作
8. 查看宏工作队列,有 2 个,别离是输入 time1 和 timer2 的
9. 先将第一个 setTimeout 出队列,输入 'timer1'
10. 碰到第三个 setTimeout,将它丢进宏工作队列,排在第二个之后
11. 第一个 setTimeout 没有微工作,所以本次宏工作执行结束
12. 第二个 setTimeout 出队列,输入 'timer2'
13. 此时它也没用微工作,所以本次宏工作再次执行结束
14. 第三个 setTimeout 出队列,输入 'timer3'
15. 第三个也没用微工作,所以执行结束
16. 此时宏工作队列都执行结束,出工
*/
6.4 题目四
返回目录
setTimeout(() => {console.log('timer1');
Promise.resolve().then(() => {console.log('promise');
});
}, 0);
setTimeout(() => {console.log('timer2');
}, 0);
console.log('start');
/**
执行程序和剖析:程序:* 'start'
* 'timer1'
* 'promise'
* 'timer2'
留神:node V10.16.0 的输入版本有所不同
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script 这个宏工作
3. 碰到第一个 setTimeout,丢进宏工作队列
4. 这里的 setTimeout 为输入 'timer1' 的一整个 setTimeout,此时并不会进入外面执行代码
5. 碰到第二个 setTimeout,丢进宏工作队列
6. 输入 'start'
7. 查看微工作,并没有微工作,所以执行下一个宏工作
8. 查看宏工作队列,有 2 个,别离是输入 time1 和 timer2 的
9. 先将第一个 setTimeout 出队列,输入 'timer1'
10. 碰到 Promise.then(),将它丢进此时的微工作队列
11. 步骤 10 寄存了第一个 setTimeout 的一个微工作,执行并输入 'promise'
12. 第二个 setTimeout 出队列,输入 'timer2'
13. 此时它也没用微工作,所以本次宏工作再次执行结束
14. 此时宏工作队列都执行结束,出工
*/
6.5 题目五
返回目录
Promise.resolve().then(() => {console.log('promise1');
const timer2 = setTimeout(() => {console.log('timer2');
}, 0);
});
const timer1 = setTimeout(() => {console.log('timer1');
Promise.resolve().then(() => {console.log('promise2');
});
}, 0);
console.log('start');
/**
执行程序和剖析:程序:* 'start'
* 'promise1'
* 'timer1'
* 'promise2'
* 'timer2'
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script 这个宏工作
3. 碰到 Promise.then(),将其推动微工作队列,留神不会执行外面内容
4. 碰到 timer1,将其推动宏工作队列
5. 输入 'start'
6. 查看 script 中的宏工作队列,发现 Promise.then(),将其推出执行
7. 输入 'promise1'
8. 碰到 timer2,将其推动宏工作队列
9. script 没有残余的微工作,所以持续遍历宏工作
10. 发现队列 [timer1, timer2],依据队列先进先出准则,推出 timer1
11. 输入 'timer1',发现微工作 Promise.then(),将其推动 timer1 的微工作队列
12. 输入 `promise2`
13. 继续执行宏工作队列,出队 timer2,输入 'timer2'
*/
6.6 题目六
返回目录
const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');
}, 0);
});
const promise2 = promise1.then(() => {throw new Error('error!');
});
console.log('promise1-1', promise1);
console.log('promise2-1', promise2);
setTimeout(() => {console.log('promise1-2', promise1);
console.log('promise2-2', promise2);
}, 0);
/**
执行程序和剖析:程序:* 'promise1-1' Promise {<pending>}
* 'promise2-1' Promise {<pending>}
* 'promise1-2' Promise {<resolve>: 'success'}
* 'promise2-2' Promise {<reject>: Error: error!}
留神:在 Node v10.16.0 上运行后果不太雷同
剖析:1. 记住 script 和 setTimeout 是宏工作
2. 首先执行 script 这个宏工作
3. 碰到 promise1 这边,执行 new Promise 外面内容,将带 resolve 的 setTimeout 推入宏工作队列
4. 碰到 promise2,因为还没有进入 resolve 状态,所以这里不理睬
5. 间断两行输入,因为 promise1 和 promise2 都尚未解决,所以是 peading 状态
6. 碰到第二个 setTimeout,将其推入宏工作队列
7. 查看宏工作队列,推出第一个 setTimeout,将 Promise 状态改为 resolve
8. 执行 promise2,扭转 promise2 的状态为 reject
9. 第一个 setTimeout 执行结束,执行第二个 setTimeout
10. 输入步骤 8 和 步骤 9 中的 Promise 状态
*/
6.7 题目七
返回目录
const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('success');
console.log('timer1');
}, 0);
console.log('promise1 外面的内容');
});
const promise2 = promise1.then(() => {throw new Error('error!');
});
console.log('promise1-1', promise1);
console.log('promise2-1', promise2);
setTimeout(() => {console.log('timer2');
console.log('promise1-1', promise1);
console.log('promise2-2', promise2);
}, 0);
/**
执行程序和剖析:程序:* 'promise1 外面的内容'
* 'promise1-1' Promise {<pending>}
* 'promise2-1' Promise {<pending>}
* 'timer1'
* 'timer2'
* 'promise1-2' Promise {<resolve>: 'success'}
* 'promise2-2' Promise {<reject>: Error: error!}
留神:在 Node v10.16.0 上运行后果不太雷同
剖析:跟上一题类型类似,这里不做剖析
*/
七 .then() 链式操作
返回目录
7.1 两个参数
返回目录
在下面的题目中,咱们纵情理解了 .then()
,然而咱们并没有细讲,所以这里进行一一解说。
话不多说,先看代码:
const promise1 = new Promise((resolve, reject) => {resolve('1');
});
promise1.then((res) => {console.log('res:', res);
}, (err) => {console.log('err:', err);
}
)
const promise2 = new Promise((resolve, reject) => {reject('1');
});
promise2.then((res) => {console.log('res:', res);
}, (err) => {console.log('err:', err);
}
)
它输入啥:
res:1
err:1
所以,实际上 .then()
是接管 2 个参数的:
resolved
:如果咱们设置了resolved
状态,那么咱们就会走第一个参数。rejected
:如果咱们设置了rejected
状态,那么咱们就会走第二个参数。
这第 2 个参数和 .catch()
是差不多的。
然而,为了放弃代码的可观,倡议第二个参数改为 .catch()
,不便了解。
7.2 链式调用
返回目录
在 Promise.then()
办法中,.then()
是能够链式调用的。
Promise.resolve(1).then((res1) => {console.log('res 1:', res1); // res 1:1
return res1;
}).then((res2) => {console.log('res 2:', res2); // res 2:1
})
能够看到,下一个 .then()
,能够接管上一个 .then()
中 return
进去的内容。
当然,值得注意的是,如果咱们没有 return res1
,那么输入会变成:res 2:undefined
。
这样,如果咱们须要做一些异步操作,那么就能够应用这种办法。
话不多说,先上代码:
const red = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log('红');
resolve('红灯走完了');
}, 1000);
});
}
const green = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log('绿');
resolve('绿灯走完了');
}, 1000);
});
}
red().then((res1) => {console.log('res1:', res1);
return green();}).then((res2) => {console.log('res2:', res2);
})
/*
1s 后输入:红
res1:红灯走完了
2s 后输入:绿
res2:绿灯走完了
*/
这里边,红灯返回一个 Promise
对象,等 setTimeout
走完,状态变为 resolved
,那么就走 .then()
,继而 1s 后输入:红 -> res1:红灯走完了
。
接着,咱们将 green()
执行了,并将它的返回 return
给了下一个 .then()
。
所以,咱们会在 2s 后输入 绿 -> 绿灯走完了
。
这就是 .then()
的门门道道。
八 .catch() 捕捉问题
返回目录
在下面咱们提到过:
reject
的作用是将Promise
对象的状态从“未实现”变为“失败”(pending -> rejected
)
而 .then()
的第 2 个参数和 .catch()
是一样的作用,都是捕捉失败的内容的。
const getRandom = new Promise((resolve, reject) => {const number = Math.random() * 10; // 生成 1-10 范畴的数字
if (number > 5) {reject('数字超过 5');
} else {resolve('数字小于 5');
}
});
// 捕捉谬误形式一:应用 catch()
getRandom.then((res) => {console.log('res:', res);
}).catch((error) => {console.log('error:', error);
});
// 捕捉谬误形式二:then() 有两个参数
getRandom.then((res) => {// 等同于 then()
console.log('res:', res);
}, (error) => {// 等同于 catch()
console.log('error:', error);
},
);
/*
输入:* res:数字小于 5
* error:数字超过 5
两者中随机一个
*/
当然,为了浏览可观,倡议还是应用 .then().catch()
的形式,而不是通过 .then()
外面的第 2 个参数来示意。
九 .finally() 强制执行
返回目录
finally
办法用于指定不论 Promise
对象最初状态如何,都会执行的操作。
同时,.finally()
办法的回调函数是不承受任何参数的,因为它是强制执行,不须要依赖 Promise
的执行后果。
它实质上就是 .then()
办法的特例。
// 1. 设置一个 promise
const promise = new Promise((resolve, reject) => {
// 2. 设置一个 0-10 随机数
const number = Math.floor(Math.random() * 10);
// 3. 如果这个数大于 5,咱们当它胜利了
if (number > 5) {resolve('大于 5'); // resolve 相当于解决的意思
} else { // 4. 否则就是失败的
reject('小于 5'); // reject 相当于解决失败的意思
}
});
// 5. 如果是 resolve,那就走 .then;如果是 reject,那就走 .catch
promise.then((res) => {console.log('胜利:', res);
}).catch((error) => {console.log('失败:', error);
}).finally(() => {
// 6. 留神 finally 就是剧终的意思,不论是好终局还是坏终局,都是终局
console.log('不论后面产生了啥,我都会调用');
});
咱们拿结尾的这份代码来回顾下 .finally()
吧:
输入 1
失败:小于 5
不论后面产生了啥,我都会调用
输入 2
胜利:大于 5
不论后面产生了啥,我都会调用
因为咱们用了随机值,所以下面 2 种输入都有可能。
然而,不论是哪种输入,.finally()
是肯定会走进去的。
十 题库:.then()、.catch()、.finally()
返回目录
10.1 题目一
返回目录
const promise = new Promise((resolve, reject) => {resolve('success1');
reject('error');
resolve('success2');
});
promise.then((res) => {console.log('then1:', res);
}).then((res) => {console.log('then2:', res);
}).catch((error) => {console.log('catch:', error);
});
/**
执行程序和剖析:程序:* 'then1: success1'
* 'then2: undefined'
剖析:1. 执行了 resolve('success1') 后,扭转了状态为 resolve,不在理睬 new Promise 前面的
2. 将第 1 个 .then() 增加到微工作
3. 执行第 1 个 .then(),将第 2 个 .then() 推动微工作
*/
10.2 题目二
返回目录
const promise = new Promise((resolve, reject) => {reject('error');
resolve('success2');
});
promise.then((res) => {console.log('then1:', res);
}).then((res) => {console.log('then2:' ,res);
}).catch((error) => {console.log('catch:', error);
}).then((res) => {console.log('then3:', res);
})
/**
执行程序和剖析:程序:* 'catch: error'
* 'then3: undefined'
剖析:1. 碰到 new Promise(),将 reject('error') 执行,扭转 Promise 的状态
2. 碰到 .catch(),将其推动微工作
3. 执行 .catch() 外面内容,输入 'catch: error',而后 return Promise {<pending>}
4. 执行下一个微工作 .then(),输入 then3: undefined
*/
10.3 题目三
返回目录
Promise
.resolve(1)
.then((res) => {console.log(res);
return 2;
}).catch((err) => {return 3;}).then((res) => {console.log(res);
});
/**
执行程序和剖析:程序:* 1
* 2
剖析:1. resolve(1) 走了第一个 .then,打印 1
2. return 2 会被包装成 resolve(2)
3. 因为没有 reject,所以不走 .catch
4. 走完第 2 个 .then,打印 2
*/
10.4 题目四
返回目录
Promise
.reject(1)
.then((res) => {console.log(res);
return 2;
}).catch((err) => {console.log(err);
return 3;
}).then((res) => {console.log(res);
});
/**
执行程序和剖析:程序:* 1
* 3
剖析:1. reject(1) 会走 .catch,所以先输入 1
2. return 3 会被包装成 resolve(3)
3. 所以持续走第 2 个 .then,输入 3
*/
10.5 题目五
返回目录
const promise = new Promise((resolve, reject) => {setTimeout(() => {console.log('timer');
resolve('success');
}, 0);
});
const start = Date.now();
promise.then((res) => {console.log(res, Date.now() - start);
});
promise.then((res) => {console.log(res, Date.now() - start);
});
/**
执行程序和剖析:程序:* 'timer'
* 'success 4'
* 'success 4'
正文:也有 3/4 或者 4/5 的状况
剖析:1. new Promise 将 setTimeout 增加进宏工作
2. 执行完宏工作 script,而后就执行 setTimeout
3. 输入 'timer'
4. 标记 Promise 状态为 resolve
5. 将第一个 .then() 放进微工作
6. 将第二个 .then() 放进微工作
7. 因为步骤 5 和步骤 6 的时候,这两者都是雷同 resolve 值,所以都是 'success'
8. 输入 success 4
9. 输入 success 4
10. 如果执行比较慢,那么这两个输入的值会不统一。例如 3、4
*/
10.6 题目六
返回目录
Promise.resolve().then(() => {return new Error('error!');
}).then((res) => {console.log('then:', res);
}).catch((err) => {console.log('catch:', err);
});
/**
执行程序和剖析:程序:* 'then: Error: error!'
剖析:return new Error('error!') 会被包裹成 return Promise.resolve(new Error('error!')) 返回到 .then()
*/
10.7 题目七
返回目录
const promise = Promise.resolve().then(() => {return promise;});
promise.catch((err) => {console.log(err);
});
/**
执行程序和剖析:程序:* TypeError: Chaining cycle detected for promise #<Promise>
剖析:不能返回 promise 自身,会造成死循环
*/
10.8 题目八
返回目录
Promise
.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log);
/**
执行程序和剖析:程序:* 1
剖析:1. .then 和 .catch 的参数心愿是函数,传入非函数会产生值透传
2. 值透传导致第 1 个 then 和第 2 个 then 传入的都不是函数,导致它传到最初的 1 个 then 外面
*/
10.9 题目九
返回目录
Promise
.reject('err')
.then((res) => {console.log('success:', res);
}, (err) => {console.log('error:', err);
}).catch((err) => {console.log('catch:', err);
})
/**
执行程序和剖析:程序:* 'error: err'
剖析:reject('err') 会进入 Promise.then 的第二个参数,所以输入 'error: err'
*/
如果本题中的 .then()
中的第 2 个参数去掉了,那么就会进入 .catch()
函数中。
10.10 题目十
返回目录
Promise
.resolve()
.then((res) => {throw new Error('error!');
}, (err) => {console.log('error:', err);
}).catch((err) => {console.log('catch:', err);
})
/**
执行程序和剖析:程序:* catch: Error: error!
剖析:因为是 .resolve(),所以会执行 .then 第 1 个参数,而后 return 的值到 .catch 中
而不是返回第 2 个参数上
*/
10.11 题目十一
返回目录
.finally()
办法不论Promise
对象最初的状态如何都会执行。.finally()
办法的回调函数不承受任何的参数,也就是说你在.finally()
函数中是没法晓得Promise
最终的状态是resolved
还是rejected
的。- 它最终返回的默认会是一个上一次的
Promise
对象值,不过如果抛出的是一个异样则返回异样的Promise
对象。
Promise
.resolve('1')
.then((res) => {console.log(res);
}).finally(() => {console.log('finally1');
});
Promise
.resolve('2')
.finally(() => {console.log('finally2');
return '这里是 finally2';
}).then((res) => {console.log('finally2 前面的 then 函数', res);
})
/**
执行程序和剖析:程序:* 1
* 'finally2'
* 'finally1'
* 'finally2 前面的 then 函数 2'
剖析:*/
10.12 题目十二
返回目录
Promise
.resolve('1')
.finally(() => {console.log('finally1');
return new Error('我是 finally1 中抛出的异样');
}).then((res) => {console.log('finally 前面的 then 函数:', res);
}).catch((err) => {console.log('捕捉谬误:', err);
})
/**
执行程序和剖析:程序:* 'finally1'
* 捕捉谬误: Error: 我是 finally1 中抛出的异样
剖析:1. 碰到 resolve('1'),将 Promise 的状态改为 resolve
2. 碰到 .finally(),丢进微工作,前面 .then() 和 .catch() 须要等 .finally() 执行结束
3. 执行 .finally(),输入 'finally1',而后执行 throw new Error,这里会将这种状况交由到 .catch()
4. 输入 捕捉谬误: Error: 我是 finally1 中抛出的异样
补充:这里如果是 return throw new Error('') 会产生什么状况呢?它会走 .then(),因为它是 return 的模式,从而输入:finally 前面的 then 函数: 1
留神这里的 return new Error('') 并没有返回函数,所以 1 穿透了
*/
10.13 题目十三
返回目录
function promise1() {let p = new Promise((resolve) => {console.log('promise1');
resolve('1');
});
return p;
}
function promise2() {return new Promise((resolve, reject) => {reject('error');
});
}
promise1().then((res) => {console.log(res);
}).catch((err) => {console.log(err);
}).finally(() => {console.log('finally1');
})
promise2().then((res) => {console.log(res);
}).catch((err) => {console.log(err);
}).finally(() => {console.log('finally2');
})
/**
执行程序和剖析:程序:* 'promise1'
* '1'
* 'error'
* 'finally1'
* 'finally2'
剖析:1. 执行 promise1(),进入外面代码
2. 碰到 p,将 p 内容执行一遍,打印 'promise1',同时将其状态改为 resolve
3. 此时 promise1() 将 .then() 这个微工作推动微工作队列,咱们记为微 1
4. 步骤 1 到 3,promise1() 执行结束
5. 开始执行 promise2(),碰到 return new Promise,将其状态改为 reject
6. 碰到 promise2() 中的 .then(),将其推动微工作队列,记为微 2
7. script 这个宏工作执行结束,开始执行微工作队列
8. 推出微 1,打印 '1',因为下面咱们传的 p 是 resolve('1')
9. 将 promise1() 外面的 finally1 推入微工作队列,记为微 3
10. 推出微 2,因为后面标记的时候,传的值是 'error',所以咱们输入 'error'
11. 推出微 3,输入 'finally1'
12. 推出微 4,输入 'finally2'
*/
10.14 题目十四
返回目录
function promise1() {let p = new Promise((resolve) => {console.log('promise1');
resolve('1');
});
return p;
}
function promise2() {return new Promise((resolve, reject) => {reject('error');
});
}
promise1().then((res) => {console.log(res);
}).catch((err) => {console.log(err);
}).then(() => {console.log('then1');
})
promise2().then((res) => {console.log(res);
}).catch((err) => {console.log(err);
}).then(() => {console.log('then2');
})
/**
执行程序和剖析:程序:* 'promise1'
* '1'
* 'error'
* 'then1'
* 'then2'
剖析:推导过程跟上题一样
*/
十一 .all() 接力赛
返回目录
Promise 的 all
办法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。
假如有代码:
const p = Promise.all([p1, p2, p3]);
p
的状态由 p1
、p2
、p3
决定,分成两种状况。
- 只有
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。 - 只有
p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
所以,联合一下 setTimeout
当做异步函数,咱们尝试下使用 .all()
办法:
const one = new Promise((resolve) => {setTimeout(() => {console.log('one');
resolve('one');
}, 1000);
})
const two = new Promise((resolve) => {setTimeout(() => {console.log('two');
resolve('two');
}, 3000);
})
const three = new Promise((resolve) => {setTimeout(() => {console.log('three');
resolve('three');
}, 2000);
})
/*
先输入:* one
* three
* two
再输入 res:['one', 'two', 'three']
*/
Promise.all([one, two, three]).then((res) => {console.log(res); // ['one', 'two', 'three']
});
这是所有状态都胜利的,如果这 3 个中有 1 个是失败的呢?请自行尝试。
十二 .race() 个人赛
返回目录
和 all()
办法不同,Promise.race()
办法是谁先走完谁先输入。
就好比跑步较量的集体 100 米赛跑一样,拿第 1 的才值得被人记住,其余的就不必过多理睬了。
将下面的例子中,.all()
改为 .race()
,则会失去不一样的后果。
const one = new Promise((resolve) => {setTimeout(() => {console.log('one');
resolve('one resolve');
}, 1000);
})
const two = new Promise((resolve) => {setTimeout(() => {console.log('two');
resolve('two resolve');
}, 3000);
})
const three = new Promise((resolve) => {setTimeout(() => {console.log('three');
resolve('three resolve');
}, 2000);
})
/*
先输入 one
再输入:one resolve
最初依序输入:* three
* two
*/
Promise.race([one, two, three]).then((res) => {console.log(res); // 'one'
});
十三 题库:.all()、.race()
返回目录
Promise.all()
和 Promise.race()
用法:
.all()
作用是接管一组异步工作,而后并行执行异步工作,并且在所有异步操作执行完后才执行回调。.race()
作用也是接管一组异步工作,而后并行执行异步工作,只保留取第一个执行实现的异步操作的后果,其余的办法仍在执行,不过执行后果会被摈弃。
小总结:
Promise.all().then()
后果中的数组的程序和Promise.all()
接管到的数组的程序统一,并不会因为setTimeout
的输入而扭转。Promise.all()
和Promise.then()
碰到会抛出异样的状况,都只会抛出最先呈现问题的那个,被.then()
的第二个参数或者.catch()
捕捉,然而不会影响数组中其余的异步工作的执行。
13.1 题目一
返回目录
在这之前咱们先做下 setTimeout
知识点的温习:
setTimeout(() => {console.log('2');
}, 2000);
setTimeout(() => {console.log('1-1');
}, 1000);
setTimeout(() => {console.log('1-2');
}, 1000);
setTimeout(() => {console.log('0');
}, 0);
这块的输入是啥?
0 -> 1-1 -> 1-2 -> 2
不便记忆,咱们能够认为:
- 同为宏工作,多个
setTimeout
,能够了解为,工夫小的会被排在后面。
所以,本来咱们的宏工作应该是个队列,然而正如在下面的 script
宏工作中,咱们增加了 4 个 setTimeout
,而这种雷同的 setTimeout
,咱们依照工夫进行排序。
这时候看第一题:
function runAsync(x) {const p = new Promise((resolved, reject) => {if (x % 2 === 0) {return setTimeout(() => {console.log(x);
resolved(x);
}, 2000);
}
return setTimeout(() => {console.log(x);
resolved(x);
}, 1000);
});
return p;
}
Promise.all([runAsync(1),
runAsync(2),
runAsync(3)
]).then((res) => {console.log(res);
})
/**
执行程序和剖析:程序:* 1
* 3
* 2
* [1, 2, 3]
剖析:1. Promise.all 将 3 个 runAsync 按程序增加进办法中
2. 在 script 这个宏工作中,顺次增加 3 个 setTimeout
3. 依据工夫对宏工作队列中的 setTimeout 进行从新排序
4. 1、2、3 对应的秒数为 1s、2s、1s,所以排序为 1 -> 3 -> 2
5. 期待一秒后,别离输入 1、3
6. 期待两秒后,输入 2
7. 执行 .then(),按照 .all() 中数组的排序输入对应的数组后果(怎么进来怎么进来)实用场景:须要事后加载多种图片、动态文件等,能够通过 Promise.all() 进行解决
*/
13.2 题目二
返回目录
function runAsync (x) {const p = new Promise((res, rej) => {if (x === 3) {return setTimeout(() => {rej(x, console.log(x));
}, 500);
}
return setTimeout(() => {res(x, console.log(x));
}, 1000);
});
return p;
}
function runReject (x) {const p = new Promise((res, rej) => {return setTimeout(() => {rej(x, console.log(x));
}, 1000 * x);
});
return p;
}
Promise.all([runAsync(1),
runReject(4),
runAsync(3),
runReject(2),
]).then((res) => {console.log('then:', res);
}, (err) => {console.log('err:', err);
}).catch((err) => {console.log('catch:', err);
})
/**
执行程序和剖析:程序:* 3
* err: 3
* 1
* 2
* 4
剖析:1. 首先,咱们当 .all() 是一个队列,先进先出
2. 此时宏工作顺次增加 setTimeout(1)、setTimeout(4)、setTimeout(3)、setTimeout(2)
3. OK,咱们在后面说过,雷同 setTimeout 会被排序,所以程序变为 3 -> 1 -> 2 -> 4
4. 这时候的 setTimeout 对应的工夫为 500ms、1s、2s、4s
5. 而后,须要记住一点新个性:.catch 只能捕捉 .all 外面最先的那个异样,并且只执行一次
6. 所以,先执行 3 的时候,会顺次输入 3 -> err: 3
7. 前面的 2 和 4 的异样不再抛出,顺次输入 1 -> 2 -> 4
*/
13.3 题目三
返回目录
function runAsync(x) {const p = new Promise((resolved, reject) => {if (x % 2 === 0) {return setTimeout(() => {console.log(x);
resolved(x);
}, 2000);
}
return setTimeout(() => {console.log(x);
resolved(x);
}, 1000);
});
return p;
}
Promise.race([runAsync(2),
runAsync(1),
runAsync(3)
]).then((res) => {console.log('res:', res);
})
/**
执行程序和剖析:程序:* 1
* 'res: 1'
* 3
* 2
留神:Node v10.16.0 的答案略有不同
剖析:1. Promise.race() 将 3 个 runAsync 按程序增加进办法中
2. 在 script 这个宏工作中,顺次增加 3 个 setTimeout 宏工作:2 -> 1 -> 3
3. 依据工夫对宏工作队列中的 setTimeout 进行从新排序
4. 1、2、3 对应的秒数为 1s、2s、1s,所以排序为 1 -> 3 -> 2
5. 期待一秒后,输入 1
6. 此时 .race() 急不可待得想通知你后果,跟着输入 res: 1
7. 紧接着输入 3
8. 期待两秒后,输入 2
实用场景:用 race 给某个异步申请设置超时工夫,并且在超时后执行相应的操作
*/
13.4 题目四
返回目录
function runAsync (x) {const p = new Promise((res, rej) => {if (x === 3) {return setTimeout(() => {rej(x, console.log(x));
}, 500);
}
return setTimeout(() => {res(x, console.log(x));
}, 1000);
});
return p;
}
function runReject (x) {const p = new Promise((res, rej) => {return setTimeout(() => {rej(x, console.log(x));
}, 1000 * x);
});
return p;
}
Promise.race([runAsync(1),
runReject(4),
runAsync(3),
runReject(2),
]).then((res) => {console.log('then:', res);
}, (err) => {console.log('err:', err);
}).catch((err) => {console.log('catch:', err);
})
/**
执行程序和剖析:程序:* 3
* err: 3
* 1
* 2
* 4
剖析:1. 首先,咱们当 .race() 是一个队列,先进先出
2. 此时宏工作顺次增加 setTimeout(1)、setTimeout(4)、setTimeout(3)、setTimeout(2)
3. OK,咱们在后面说过,雷同 setTimeout 会被排序,所以程序变为 3 -> 1 -> 2 -> 4
4. 这时候的 setTimeout 对应的工夫为 500ms、1s、2s、4s
5. 而后,须要记住一点:.race() 只会跑最先的那个
6. 所以,先执行 3 的时候,会顺次输入 3 -> err: 3
7. 前面的 2 和 4 的异样不再抛出,顺次输入 1 -> 2 -> 4
*/
十四 Promise 源码
返回目录
在 jsliang 手写源码系列中有详细分析。
- jsliang 手写源码系列:Promise
十五 题库:联合 async/await
返回目录
总结:
- 在
function()
外面碰到await
间接走外面内容。 - 如果
function()
里的await
前面还有其余代码,将其当做Promise.then()
一样,视为微工作。
15.1 题目一
返回目录
async function async1() {console.log(1);
await async2();
console.log(2);
}
async function async2() {console.log(3);
}
async1();
console.log(4);
/**
执行程序和剖析:程序:* 1
* 3
* 4
* 2
剖析:1. 首先,咱们执行 script 这个宏工作
2. 碰到 async1(),执行外面代码,输入 1
3. 碰到 await async2(),阻塞了,所以须要先执行 async2()
4. 执行 async2(),输入 3
5. 碰到 console.log(4),输入 4
6. 阻塞局部走完了,script 这个宏工作也走完了,接着走 async1() 前面的
7. 输入 2
*/
15.2 题目二
返回目录
async function async1() {console.log('async');
new Promise((resolve) => {console.log('promise');
resolve();}).then((res) => {console.log('promise.then');
})
}
async1();
console.log('start');
/**
执行程序和剖析:程序:* 'async'
* 'promise'
* 'start'
* 'promise.then'
剖析:1. 首先,咱们执行 script 这个宏工作
2. 碰到 async1(),执行外面代码,输入 'async'
3. 碰到 new Promise,执行外面代码,输入 'promise'
4. 将 Promise 的状态标记为 resolved
5. 将 .then() 丢进微工作
6. 输入 'start'
7. 执行微工作 .then(),输入 'promise.then'
*/
15.3 题目三
返回目录
async function async1() {console.log('async1 start');
setTimeout(() => {console.log('timer1 start');
}, 0);
Promise.resolve().then((res) => {console.log('promise1');
})
await async2();
setTimeout(() => {console.log('timer1 end');
}, 0);
console.log('async1 end');
}
async function async2() {setTimeout(() => {console.log('timer2');
}, 0);
Promise.resolve().then((res) => {console.log('promise2');
})
console.log('async2');
}
async1();
console.log('start');
/**
执行程序和剖析:程序:* 'async1 start'
* 'async2'
* 'start'
* 'promise1'
* 'promise2'
* 'async1 end'
* 'timer1 start'
* 'timer2'
* 'timer1 end'
剖析:1. 首先,咱们理顺一个事实:在 await 前面的,会等以后宏工作外面所有微工作执行结束,方且执行
2. 碰到 async1(),开始执行外面内容
3. 输入 'async1 start'
4. 将 'timer1 start' 丢进宏工作队列,标记为宏 1
5. 将 'promise1' 丢进微工作队列,标记为微 1
6. 碰到 await async2(),先执行 async2,阻塞上面的代码,标记前面代码为马后炮 1
7. 执行 async2,碰到 'timer2',将其丢进宏工作队列,标记为宏 2
8. 碰到 'promise2',将其丢进微工作队列,标记为微 2
9. 输入 'async2'
10. async2 走完,持续往下走,输入 start
11. 以后有 3 个局部咱们没走,别离是微 1、微 2 和马后炮 1
12.【死记】,碰到不走 11 这种状况,咱们须要记住先执行以后微工作,再马后炮
13. 执行微工作,输入 'promise1'、'promise2'
14. 执行马后炮,将 'timer1 end' 丢进宏工作队列,即为宏 3
15. 输入 'async1 end'
16. 顺次执行宏 1、宏 2 和 宏 3,输入 'timer1 start' -> 'timer2' -> 'timer1 end'
灵魂晋升:如果 'timer1 start' -> 'timer2' -> 'timer1 end' 对应的工夫别离为 500ms、1000ms、500ms,请问输入啥?*/
15.4 题目四
返回目录
async function async1() {console.log('async1 start');
await async2();
console.log('async1 end');
setTimeout(() => {console.log('timer1');
}, 0);
}
async function async2() {setTimeout(() => {console.log('timer2');
}, 0);
console.log('async2');
}
async1();
setTimeout(() => {console.log('timer3');
}, 0);
console.log('start');
/**
执行程序和剖析:程序:* 'async1 start'
* 'async2'
* 'start'
* 'async1 end'
* 'timer2'
* 'timer3'
* 'timer1'
剖析:思路同上一道题
*/
15.5 题目五
返回目录
async function fn() {return 123;}
fn().then((res) => {console.log(res);
})
/**
执行程序和剖析:程序:* 123
剖析:失常状况下,async 中的 await 命令是一个 Promise 对象,返回该对象的后果
但如果不是 Promise 对象的话,就会间接返回对应的值,相当于 Promise.resolve();
*/
15.6 题目六
返回目录
async function async1() {console.log('async1 start');
await new Promise((resolve) => {console.log('promise1');
})
console.log('async1 success');
return 'async1 end';
}
console.log('script start');
async1().then((res) => {console.log('res:', res);
})
console.log('script end');
/**
执行程序和剖析:程序:* 'script start'
* 'async1 start'
* 'promise1'
* 'script end'
剖析:1. 非凡题
2. 在 await 前面的 Promise 是没有返回值的,所以 await 会始终期待
3. 这样子的话,async1 success 这些前面的内容都不会执行了
思考:如果在 'promise1' 前面增加一行 resolve('123'); 后果会怎么?*/
15.7 题目七
返回目录
async function async1() {console.log('async1 start');
await new Promise((resolve) => {console.log('promise1');
resolve('promise resolve');
})
console.log('async1 success');
return 'async1 end';
}
console.log('script start');
async1().then((res) => {console.log('res:', res);
})
new Promise((resolve) => {console.log('promsie2');
setTimeout(() => {console.log('timer');
}, 0);
})
/**
执行程序和剖析:程序:* 'script start'
* 'async1 start'
* 'promise1'
* 'promsie2'
* 'async1 success'
* 'res: async1 end'
* 'timer'
剖析:1. 紧跟上一题的剖析,Promise 必须 resolve 了,await 前面的代码才会继续执行
2. 先走整体 script 宏工作,输入 'script start'
3. 碰到 async1() 的执行,走外面去看看
4. 输入 'async1 start'
5. 碰到 await new Prmise
6. 输入 'promise1'
7. 看到 resolve,扭转 Promise 状态,告知 await 有期待对象,将前面的内容丢进微工作 1
8. 往下执行前面的 new Promsie
9. 输入 'promsie2'
10. 将 setTimeout 丢进宏工作 1
11. 当初有一个微工作 1 和一个宏工作 1
12. 先走微工作 1
13. 输入 'async1 success'
14. 碰到 return,告知前面增加一个微工作 2
15. 继续执行微工作 2,输入 'res: async1 end'
16. 没有其余微工作了,输入宏工作队列,输入 'timer1'
*/
15.8 题目八
返回目录
async function async1() {console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {console.log('async2');
}
console.log('script start');
setTimeout(() => {console.log('settimeout');
}, 0);
async1();
new Promise((resolve) => {console.log('promise1');
resolve();}).then((res) => {console.log('promise2');
})
console.log('script end');
/**
执行程序和剖析:程序:* 'script start'
* 'async1 start'
* 'async2'
* 'promise1'
* 'script end'
* 'promise2'
* 'async1 end'
* 'settimeout'
剖析:到了这里就不须要解释了,跟下面题目相似
*/
15.9 题目九
返回目录
async function async1() {console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {console.log('async2');
}
console.log('script start');
setTimeout(() => {console.log('settimeout');
}, 0);
async1();
new Promise((resolve) => {console.log('promise1');
resolve();}).then((res) => {console.log('promise2');
})
console.log('script end');
/**
执行程序和剖析:程序:* 'script start'
* 'async1 start'
* 'async2'
* 'promise1'
* 'script end'
* 'async1 end'
* 'promise2'
* 'settimeout'
留神:这里的输入 'async1 end' 和 'promise2',在 Node v10.16.0 是反过来的
剖析:到了这里就不须要解释了,跟下面题目相似
*/
15.10 题目十
返回目录
async function testSomething() {console.log('test something');
return 'test something';
}
async function testAsync() {console.log('test async');
return Promise.resolve('hello test async');
}
async function test() {console.log('test start');
const v1 = await testSomething();
console.log('v1:', v1);
const v2 = await testAsync();
console.log('v2:', v2);
console.log(v1, v2);
}
test();
const promise = new Promise((resolve) => {console.log('promise start');
resolve('promise');
})
promise.then((val) => {console.log(val);
})
console.log('test end');
/**
执行程序和剖析:程序:* 'test start'
* 'test something'
* 'promise start'
* 'test end'
* 'v1: test something'
* 'test async'
* 'promise'
* 'v2: hello test async'
* 'test something' 'hello test async'
留神:这里的输入在 Node v10.16.0 是不同的
剖析:到了这里就不须要解释了,跟下面题目相似
*/
15.11 题目十一
返回目录
开始做 async
处理错误的题。
async function async1() {await async2();
console.log('async1');
return 'async1 success';
}
async function async2() {return new Promise((resolve, reject) => {console.log('async2');
reject('error');
})
}
async1().then((res) => {console.log('res:', res);
})
/**
执行程序和剖析:程序:* 'async2'
* Promise {<rejected>: "error"}
剖析:如果在 async 函数中抛出了谬误,则终止谬误后果,不会持续向下执行。throw new Error 也是如此。*/
十六 综合题
返回目录
16.1 题目一
返回目录
const first = () => (new Promise((resolve1, reject1) => {console.log(3);
const p = new Promise((resolve2, reject2) => {console.log(7);
setTimeout(() => {console.log(5);
resolve1(6);
console.log(p);
}, 0);
resolve2(1);
});
resolve1(2);
p.then((res1) => {console.log('res1:', res1);
});
}));
first().then((res2) => {console.log('res2:', res2);
});
console.log(4);
/**
执行程序:* 3
* 7
* 4
* res: 1
* res: 2
* 5
* Promise{<resolve> 1}
*/
16.2 题目二
返回目录
const async1 = async() => {console.log('async1');
setTimeout(() => {console.log('timer1');
}, 2000);
await new Promise((resolve) => {console.log('promise1');
})
console.log('async1 end');
return 'async1 success';
};
console.log('script start');
async1().then((res1) => {console.log('res1:', res1);
})
console.log('script end');
Promise
.resolve(1)
.then(2)
.then(Promise.resolve(3))
.catch(4)
.then((res2) => {console.log('res2:', res2);
})
setTimeout(() => {console.log('timer2');
}, 1000);
/**
执行程序:* 'script start'
* 'async1'
* 'promise1'
* 'script end'
* 'res2: 1'
* 'timer2'
* 'timer1'
*/
16.3 题目三
返回目录
const p1 = new Promise((resolve) => {setTimeout(() => {resolve('resolve3');
console.log('timer1');
}, 0);
resolve('resolve1');
resolve('resolve2');
}).then((res) => {console.log(res);
setTimeout(() => {console.log(p1);
}, 1000);
}).finally((res) => {console.log('finally:', res);
})
/**
执行程序:* 'resolve1'
* 'finally: undefined'
* 'timer1'
* 'Promise {<resolved> undefined}'
*/
十七 大厂题
返回目录
17.1 应用 Promise 实现每隔一秒输入 1、2、3
返回目录
const oneToThree = () => {const arr = [1, 2, 3];
arr.reduce((prev, next) => {return prev.then(() => {return new Promise((resolve) => {setTimeout(() => {console.log(next);
resolve();}, 1000);
})
});
}, Promise.resolve())
};
console.log(oneToThree());
改写 forEach:
/**
* 实现函数 forEach(arr, cb),使 cb 一一解决 arr 中的元素
* 一次解决可能是同步的,也可能是异步的
* 要求解决实现以后元素能力解决下一个。* 提醒:cb 函数执行如果是异步,会返回一个 Promise
*/
const liangEach = (arr, cb) => {arr.reduce((prev, next) => {return prev.then((res1) => {return cb(next);
});
}, Promise.resolve())
};
const arr = [1, 2, 5];
liangEach(arr, (n) => {return new Promise((resolve) => {setTimeout(() => {console.log(n);
resolve();}, 1000);
})
})
如果有 async/await
实现:
/**
* 实现函数 forEach(arr, cb),使 cb 一一解决 arr 中的元素
* 一次解决可能是同步的,也可能是异步的
* 要求解决实现以后元素能力解决下一个。* 提醒:cb 函数执行如果是异步,会返回一个 Promise
*/
async function liangEach(arr, cb) {for (let i = 0; i < arr.length; i++) {await cb(arr[i]);
}
};
const arr = [1, 2, 5];
liangEach(arr, (n) => {return new Promise((resolve) => {setTimeout(() => {console.log(n);
resolve();}, 1000);
})
});
17.2 应用 Promise 实现红绿灯交替反复亮
返回目录
红灯 3 秒亮一次,黄灯 2 秒亮一次,绿灯 1 秒亮一次,用 Promise 实现 3 个灯交替反复亮。
已知函数:
function red() {console.log('red');
}
function yellow() {console.log('yellow');
}
function green() {console.log('green');
}
答案:
function red() {console.log('red');
}
function yellow() {console.log('yellow');
}
function green() {console.log('green');
}
const light = (timer, cb) => {return new Promise((resolve) => {setTimeout(() => {cb();
resolve();}, timer);
})
}
const step = () => {Promise.resolve().then(() => {return light(3000, red);
}).then(() => {return light(2000, yellow);
}).then(() => {return light(1000, green);
}).then(() => {return step();
})
};
step();
17.3 实现 mergePromise 函数
返回目录
实现 mergePromise
函数,将传进去的数组按先后顺序执行,并且把返回的值先后放在数组 data
中。
例如:
const time = (timer) => {
return new Promise(resolve => {setTimeout(() => {resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {console.log(3);
return 3
})
function mergePromise () {// 在这里写代码}
mergePromise([ajax1, ajax2, ajax3]).then(data => {console.log("done");
console.log(data); // data 为 [1, 2, 3]
});
// 要求别离输入
// 1
// 2
// 3
// done
// [1, 2, 3]
答案:
const time = (timer) => {
return new Promise(resolve => {setTimeout(() => {resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {console.log(3);
return 3
})
function mergePromise (ajaxList) {const data = [];
let promise = Promise.resolve();
ajaxList.forEach((ajax) => {promise = promise.then(() => {return ajax();
}).then((resolve) => {data.push(resolve);
return data;
})
})
return promise;
}
mergePromise([ajax1, ajax2, ajax3]).then(data => {console.log("done");
console.log(data); // data 为 [1, 2, 3]
});
// 要求别离输入
// 1
// 2
// 3
// done
// [1, 2, 3]
17.4 依据 PromiseA+ 实现一个本人的 Promise
返回目录
在 jsliang 手写源码系列中有详细分析。
- jsliang 手写源码系列:Promise
17.5 封装一个异步加载图片的办法
返回目录
function loadImg(url) {// ... 实现代码}
答案:
function loadImg(url) {return new Promise((resolve, reject) => {const image = new Image();
image.onload = () => {console.log('图片加载实现');
resolve(image);
}
image.onerror = () => {reject(new Error('加载失败' + url));
}
image.src = url;
})
}
17.6 限度异步操作并发数并尽可能快地实现
返回目录
已知图片列表:
var urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
已知函数:
function loadImg(url) {return new Promise((resolve, reject) => {const img = new Image();
img.onload = function() {console.log("一张图片加载实现");
resolve(img);
};
img.onerror = function() {reject(new Error('Could not load image at' + url));
};
img.src = url;
});
};
function limitLoad(urls, handler, limit) {// ... 实现代码}
求同时下载的链接纯熟不超过 3 个的状况下,尽可能快地实现。
const urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {return new Promise((resolve, reject) => {const img = new Image();
img.onload = function() {console.log("一张图片加载实现");
resolve(img);
};
img.onerror = function() {reject(new Error('Could not load image at' + url));
};
img.src = url;
});
}
function limitLoad(urls, handler, limit) {let sequence = [].concat(urls); // 复制 urls
// 这一步是为了初始化 promises 这个 "容器"
let promises = sequence.splice(0, limit).map((url, index) => {return handler(url).then(() => {
// 返回下标是为了晓得数组中是哪一项最先实现
return index;
});
});
// 留神这里要将整个变量过程返回,这样失去的就是一个 Promise,能够在里面链式调用
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {return Promise.race(promises); // 返回曾经实现的下标
})
.then((fastestIndex) => {
// 获取到曾经实现的下标
// 将 "容器" 内曾经实现的那一项替换
promises[fastestIndex] = handler(url).then(() => {return fastestIndex; // 要持续将这个下标返回,以便下一次变量});
})
.catch((err) => {console.error(err);
});
}, Promise.resolve()) // 初始化传入
.then(() => {
// 最初三个用.all 来调用
return Promise.all(promises);
});
}
limitLoad(urls, loadImg, 3)
.then((res) => {console.log("图片全副加载结束");
console.log(res);
})
.catch((err) => {console.error(err);
});
17.7 JS 实现异步调度器
返回目录
审题并实现上面代码:
/**
* 题目:JS 实现异步调度器
* 要求:* JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有 2 个
* 欠缺上面代码中的 Scheduler 类,使程序能正确输入
*/
class Scheduler {add(promiseCreator) {// ...}
// ...
}
const timeout = (time) => {return new Promise((resolve) => {setTimeout(() => {resolve();
}, time);
});
};
const scheduler = new Scheduler();
const addTack = (time, order) => {
return scheduler
.add(() => timeout(time))
.then(() => console.log(order));
};
addTack(1000, '1');
addTack(500, '2');
addTack(300, '3');
addTack(400, '4');
// 输入:2 3 1 4
// 一开始,1、2 两个工作进入队列
// 500ms 时,实现 2,输入 2,工作 3 进队
// 800ms 时,实现 3,输入 3,工作 4 进队
// 1000ms 时,实现 1,输入 1,没有下一个进队的
// 1200ms 时,实现 4,输入 4,没有下一个进队的
// 进队实现,输入 2 3 1 4
实现形式(async/await
):
/**
* 题目:JS 实现异步调度器
* 要求:* JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有 2 个
* 欠缺上面代码中的 Scheduler 类,使程序能正确输入
*/
class Scheduler {constructor(maxNum) {this.taskList = [];
this.count = 0;
this.maxNum = maxNum; // 最大并发数
}
async add(promiseCreator) {
// 如果以后并发超过最大并发,那就进入工作队列期待
if (this.count >= this.maxNum) {await new Promise((resolve) => {this.taskList.push(resolve);
})
}
// 次数 + 1(如果后面的没执行完,那就始终增加)this.count++;
// 期待外面内容执行结束
const result = await promiseCreator();
// 次数 - 1
this.count--;
// 将队首出队
if (this.taskList.length) {this.taskList.shift()();}
// 链式调用,将后果值返回进来
return result;
}
}
const timeout = (time) => {return new Promise((resolve) => {setTimeout(() => {resolve();
}, time);
});
};
const scheduler = new Scheduler(2);
const addTack = (time, order) => {
return scheduler
.add(() => timeout(time))
.then(() => console.log(order));
};
addTack(1000, '1');
addTack(500, '2');
addTack(300, '3');
addTack(400, '4');
// 输入:2 3 1 4
// 一开始,1、2 两个工作进入队列
// 500ms 时,实现 2,输入 2,工作 3 进队
// 800ms 时,实现 3,输入 3,工作 4 进队
// 1000ms 时,实现 1,输入 1,没有下一个进队的
// 1200ms 时,实现 4,输入 4,没有下一个进队的
// 进队实现,输入 2 3 1 4
十八 总结
返回目录
写到这里,终于扯完了所有要扯的内容。
首先,这篇文章的大部分题目,取自 LinDaiDai_霖呆呆 的文章,非常感激他的文章,我花了 8 小时以上,把这些题目联合浏览器和 Node
的打印敲了一遍,终于大略搞通这块相干内容。
同时,无业游民期间,感谢呆呆的激励,对求职之路坚定信心。
而后,这篇文章大略是 Promise
的劝退文吧!
因为依据我编写和整顿后的预估,它须要一周左右的工夫进行浏览和大体掌控!
然而,如果你真的看完了,那么祝贺你:Promise
这块任督二脉你曾经买通了。
最初,如果小伙伴感觉文章不错,欢送各种激励(点赞、Star……),联系方式能够看集体 Github 首页,有问题也请尽量私聊,毕竟有时候真的感觉工夫不够折腾:
- jsliang 的文档库
那么,江湖有缘再见!
jsliang 的文档库由 梁峻荣 采纳 常识共享 署名 - 非商业性应用 - 雷同形式共享 4.0 国内 许可协定 进行许可。<br/> 基于 https://github.com/LiangJunrong/document-library 上的作品创作。<br/> 本许可协定受权之外的应用权限能够从 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 处取得。