写在前面
- 五一假期,来写一写 Promise 的题吧,嘿嘿嘿 😃
- 五一倒计时 3 天
实践准备
1 | #import <Foundation/Foundation.h> |
- 如上实现一个简单的事件循环,用来输出 console
- 我们在 Javascript 中讲的微任务队列、宏任务队列,是 Javascript 调用方去使用 Javascript 的一种方式,如果仅是执行一段代码,是不需要事件循环的。通过这段代码,是可以看出来,Javascript 的事件循环,是在 JSContext 之外,事件循环相关知识,既不是 Javascript 引擎的一部分,也不是 Javascript 语言的一部分。
- code 的传入方式
- <script></script> 普通代码片段
- <script type=”module”></script>
- 函数
1 | #import <Foundation/Foundation.h> |
- 上面的 Object-C 代码,可以看出来,我们是将一段一段的代码,传入 evaluateScript 中执行。
1 | #import <Foundation/Foundation.h> |
evaluateScript 实际上执行两步:
执行整个方法
new Promise(resolve => resolve()).then(() => this.a = 3), function(){return this.a};
- 逗号表达式,永远返回后面的值,如果被调用,前面的会被执行
执行 Promise 中 then 后面的语句
- 有 then ,可能产生一个宏任务里面有多个微任务的情况, 一切JS 代码都是微任务中执行的
- 拿浏览器举例:setTimeout、setInterval 这种其实不是 JS 语法本身的 API,是 JS 的宿主浏览器提供的 API, 所以是宏任务。
- 而 Promise 是 JS 本身自带的 API,这种就是微任务。
总结:宿主提供的方法是宏任务,JS 自带的是微任务
任务列表列里面有很多宏任务,然后每个宏任务里面有一个微任务列表,每个宏任务执行下一个宏任务之前会把自己内部的微任务执行完
宏任务包括:script 、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。
微任务包括:MutationObserver、Promise.then()或catch()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。
Javascript 结构化程序设计基础
实践记录
基础题
1.1 题目一
1 | const promise1 = new Promise((resolve, reject) => { |
分析:
从上至下,先执行 new Promise,执行该构造函数中 console.log(‘promise1’)
console.log(‘1’, promise1);
再执行同步代码
执行完后, promise1 中并没有 resolved, rejected, 一直处在 pending 的状态
运行结果
1.2 题目二
1 | const promise = new Promise((resolve, reject) => { |
分析
- 从上至下,先执行 new Promise,执行该构造函数中
- console.log(1);
- resolve(‘success’) 将 promise 中的状态更改为 resolved,并保存下来
- console.log(2);
- promise.then 入队微任务队列
- 再执行当前宏任务中的同步代码
- console.log(4);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列,且 promise 为resolved,执行 promise.then
- console.log(3);
- 从上至下,先执行 new Promise,执行该构造函数中
运行结果
1.3 题目三
1 | const promise = new Promise((resolve, reject) => { |
分析
- 从上至下,先执行 new Promise,执行该构造函数中
- console.log(1);
- console.log(2);
- 该 promise 没有 resolved, rejected, 一直处在 pending
- promise.then 入队微任务队列
- 再执行当前宏任务中的同步代码
- console.log(4);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列,但 promise 为pending,不可能执行 promise.then 中方法
- 从上至下,先执行 new Promise,执行该构造函数中
运行结果
1.4 题目四
1 | const promise1 = new Promise((resolve, reject) => { |
分析
- 从上至下,先执行 new Promise,执行该构造函数中
- console.log(‘promise1’)
- resolve(‘resolve1’) 将 promise 中的状态更改为 resolved,并保存下来
- promise1.then 入队微任务队列
- promise2 是一个新状态为 pending 的 Promise
- 执行同步代码
- console.log(‘1’, promise1);
- promise1 中状态在上面已经更改为 resolved
- console.log(‘2’, promise2);
- promise2 中状态为pending
- console.log(‘1’, promise1);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise1.then
- console.log(res)
- 其中 promise1.then, promise1 的状态为 resolved
- console.log(res)
- 从上至下,先执行 new Promise,执行该构造函数中
运行结果
1.5 题目五
1 | const fn = () => (new Promise((resolve, reject) => { |
分析
- 从上至下,const fn = () => (Promise Object) 返回一个 promise 对象
- fn(),执行 fn 方法
- console.log(1);
- resolve(‘success’) 将 promise 中的状态更改为 resolved,并保存下来
- Function.then 入队微任务队列
- 执行同步代码
- console.log(‘start’)
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 Function.then
- console.log(res) 其中 promise 的状态为 resolved
运行结果
1.6 题目六
1 | const fn = () => |
分析
- 从上至下,const fn = () => (Promise Object) 返回一个 promise 对象
- 执行同步代码
- console.log(“start”);
- fn(), 执行 fn 方法
- console.log(1);
- resolve(“success”); 将 promise 中的状态更改为 resolved,并保存下来
- Function.then 入队微任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 Function.then
- console.log(res); 其中 promise 的状态为 resolved
运行结果
Promise 结合 setTimeout
2.1 题目一
1 | console.log('start') |
分析
- 从上至下,先执行同步代码
- console.log(‘start’)
- setTimeout … 入队宏任务队列
- Promise.resolve().then … 入队微任务队列
- 执行同步代码
- console.log(‘end’)
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 Promise.resolve().then
- console.log(‘resolve’)
- 第一个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- console.log(‘time’)
- 从上至下,先执行同步代码
运行结果
2.2 题目二
1 | const promise = new Promise((resolve, reject) => { |
分析
- 从上至下,先执行 new Promise,执行该构造函数中
- console.log(1);
- setTimeout … 入队宏任务队列
- console.log(2);
- promise.then … 入队微任务队列
- 执行同步代码
- console.log(4);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 其中 promise 中,并没有 resolved、rejected,一直在 pending,不可能执行 promise.then 中方法
- 第一个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- 第二个宏任务 setTimeout 代码从上至下执行
- 执行同步代码
- console.log(“timerStart”);
- resolve(“success”); 将 promise 中的状态更改为 resolved,并保存下来
- console.log(“timerEnd”);
- 从上至下,先执行 new Promise,执行该构造函数中
运行结果
2.3 题目三
1 | setTimeout(() => { |
分析
- 从上执行
- setTimeout … 加入宏任务队列
- setTimeout … 加入宏任务队列
- 执行同步代码
- console.log(‘start’)
- 第一个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- 执行同步代码
- console.log(‘timer1’);
- setTimeout … 加入宏任务队列
- 第二个宏任务执行完了,开始执行下一个宏任务
- 执行同步代码
- console.log(‘timer2’)
- 第三个宏任务执行完了,开始执行下一个宏任务
- 执行同步代码
- console.log(‘timer3’)
运行结果
1 | setTimeout(() => { |
分析
- 代码从上至下开始执行
- setTimeout … 加入宏任务队列
- setTimeout … 加入宏任务队列
- 执行同步代码
- console.log(‘start’)
- 第一个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- 执行同步代码
- console.log(‘timer1’);
- Promise.resolve().then … 入队微任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列
- console.log(‘promise’)
- 第二个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- 执行同步代码
- console.log(‘timer2’)
运行结果
2.4 题目四
1 | Promise.resolve().then(() => { |
分析
- 代码从上至下开始执行
- Promise.resolve().then … 入队微任务队列
- setTimeout … 入队宏任务队列
- 执行同步代码
- console.log(‘start’);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列
- 执行同步代码
- console.log(‘promise1’);
- setTimeout … 入队宏任务队列
- 第一个宏任务执行完了,开始执行下一个宏任务 setTimeout …
- console.log(‘timer2’)
- 第二个宏任务执行完了,开始执行下一个宏任务
- 执行同步代码
- console.log(‘timer1’)
- Promise.resolve().then … 入队微任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列
- console.log(‘promise2’)
- 第二个宏任务执行完了,开始执行下一个宏任务
- console.log(‘timer2’)
运行结果
2.5 题目五 ** 存在和之前一样的,promise resolved 后,状态上抛至上一个 宏任务队列的问题
1 | const promise1 = new Promise((resolve, reject) => { |
分析
- 代码从上至下开始执行
- 先执行 new Promise,执行该构造函数中
- setTimout … 入队宏任务队列
- promise1.then … 入队微任务队列
- 执行同步代码
- console.log(‘promise1’, promise1)
- 其中 promise1 没有 resolved、rejected,一直处在 pending 状态
- promise1 Promise {<pending>}
- console.log(‘promise2’, promise2)
- 其中 promise1 一直处在 pending 状态,直接影响到 promise2 也一直处在 pending 状态
- promise2 Promise {<pending>}
- console.log(‘promise1’, promise1)
- setTimeout … 入队宏任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise1.then …
- 由于 promise1 仍处于 pending 状态,不可能执行 promise1.then …
- 第一个宏任务执行完了,开始执行下一个宏任务
- resolve(‘success’) 将 promise1 中的状态更改为 resolved,并保存下来
- 第二个宏任务执行完了,开始执行下一个宏任务
- 执行同步代码
- console.log(‘promise1’, promise1)
- 其中 promise1 处在 resolved 状态
- promise1 Promise {<resolved>: “success”}
- console.log(‘promise2’, promise2)
- promise2 Promise {<rejected>: Error: error!!! at <anonymous>:8:9}
- console.log(‘promise1’, promise1)
运行结果
2.6 题目六
1 | const promise1 = new Promise((resolve, reject) => { |
分析
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
- setTimeout 入队宏任务队列
- 执行同步代码
- console.log(“promise1里的内容”);
- promise1.then … 入队微任务队列
- 执行同步代码
- console.log(“promise1”, promise1);
- 其中 promise1 没有 resolved、rejected 掉,一直处在 pending 状态
- promise1 Promise {<pending>}
- 其中 promise1 没有 resolved、rejected 掉,一直处在 pending 状态
- console.log(“promise2”, promise2);
- primise1 的 pending 状态,直接影响 promise2 也处在 pending 状态
- promise2 Promise {<pending>}
- primise1 的 pending 状态,直接影响 promise2 也处在 pending 状态
- console.log(“promise1”, promise1);
- setTimeout … 入队宏任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise1.then …
- 由于 promise1 仍处于 pending 状态,不可能执行 promise1.then …
- 第一个宏任务执行完了,开始执行第二个宏任务
- resolve(“success”); 将 promise1 中的状态更改为 resolved,并保存下来
- 执行同步代码
- console.log(“timer1”);
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise1.then …
- throw new Error(“error!!!”);
- 第二个宏任务执行完了,开始执行下一个宏任务
- 执行同步代码
- console.log(“timer2”);
- console.log(“promise1”, promise1);
- 其中 promise1 的状态已经 resloved
- promise1 Promise {<resolved>: “success”}
- console.log(“promise2”, promise2);
- promise2 Promise {<rejected>: Error: error!!! at <anonymous>:8:9}
- 执行同步代码
运行结果
Promise 中的 then、catch、finally
3.1 题目一
1 | const promise = new Promise((resolve, reject) => { |
分析
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
- resolve(“success1”); 将 promise 中的状态更改为 resolved,并保存下来
- promise.then … 入队微任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise.then …
- console then: success1
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
运行结果
结论
- Promise的状态一经改变就不能再改变
- 构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用。
3.2 题目二
1 | const promise = new Promise((resolve, reject) => { |
分析
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
- reject(“error”); 将 promise 中的状态更改为 rejected,并保存下来
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise.then …
- 由于 promise 状态为 rejected,因此触发 catch ,不管 catch 连接到哪,都能捕获上层未捕捉过的错误
- console.log(“catch: “, err);
- return a new promise
- 由于 新的 promise 没有返回值,因此返回 undefined
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
结论
- catch不管被连接到哪里,都能捕获上层未捕捉过的错误
- 至于 then3 也会被执行,那是因为*catch()也会返回一个 Promise *,且由于这个 Promise 没有返回值,所以打印出来的是 undefined 。
运行结果
3.3 题目三
1 | Promise.resolve(1) |
分析
- 代码从上至下开始执行
- promise 状态为 resolved, 因此触发 then
- console.log(res); res –> 1
- 没有报错,略过 catch
- 被 then 捕捉
- console.log(2); 返回新的 promise, promise 返回 resolve(2)
结论
- Promise 可以链式调用,不过 promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用, 它并不像一般我们任务的链式调用一样 return this。
运行结果
3.4 题目四
1 | Promise.reject(1) |
分析
- 代码从上至下开始执行
- promise 状态为 rejected,因此触发 catch
- console.log(err); err –> 1
- return resolve(3)
- 被 then 捕捉
- console.log(3)
运行结果
3.5 题目五
1 | const promise = new Promise((resolve, reject) => { |
分析
- 代码从上至下开始执行,先执行 new Promise,执行该构造函数
- setTimeout … 入队宏任务队列
- promise.then … 入队微任务队列
- promise.then … 入队微任务队列
- 当前宏任务中的所有同步代码执行完毕,开始执行当前宏任务中的微任务队列 promise.then …
- 因为 promise 状态并没有 resolved、rejected,一直处在 pending,并不能调用 then 方法
- 同上
- 第一个宏任务执行完成,开始执行第二个宏任务 setTimeout …
- 执行同步代码
- console.log(‘timer’)
- promise 状态更改为 resolved,并保存下来
- 状态往上抛
- console.log(res, Date.now() - start)
- console.log(res, Date.now() - start))
结论
- Promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变,并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。
运行结果
3.6 题目六
1 | Promise.resolve().then(() => { |
分析
- 代码从上至下开始执行
- promise 状态更改为 resolve,并无返回值
- 执行 then
- throw new Error(‘error!!!’)
- return Promise.reject(new Error(‘error!!!’))
- 被 catch 捕捉
- console.log(“catch: “, err) err –> error
运行结果
3.7 题目七
1 | const promise = Promise.resolve().then(() => { |
分析
- 代码从上至下开始执行
- promise 状态改变为 resolve ,并无返回值
- 执行 then
- return Promise.resolve(promise)
- Chaining cycle detected for promise
- 执行同步代码
- promise.catch promise cycle
运行结果
3.8 题目八
1 | Promise.resolve(1) |
分析
- 代码从上至下开始执行
- promise 状态改变为 resolve, return Promise.resolve(1)
- .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
- 第一个then和第二个then中传入的都不是函数,一个是数字类型,一个是对象类型,因此发生了透传,将resolve(1) 的值直接传到最后一个then里
结论
- .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传
运行结果
3.9 题目九
1 | Promise.reject('err!!!') |
我们可以先来看看,如果 catch 住了错误,err 会一直链式传递下去被 catch嘛
- 一旦 catch 住当前报错,当前报错并不会再往下传递
- 逐行调用 then 是会一直传递下去的
那我还可以看看,如果没有错误,res 会一直链式传递 then 执行嘛
- 可以看出 then 是会一直往下传递的
- 逐行调用 then 是会一直传递下去的
分析
- 代码从上至下开始执行
- promise 状态改变为 rejected,return ‘err!!!’
- 执行 then,then 中第二个函数,相当于 catch
- 因此执行
- console.log(‘error’, err) err –> err!!!
运行结果
1 | Promise.resolve() |
分析
- 代码从上至下开始执行
- promise 状态变为 resolve,
- 执行 then
- throw new Error(‘error!!!’)
- 被 catch
- console.log(‘fail2’, err) err –> error!!!
运行结果
3.10 题目十
1 | Promise.resolve('1') |
分析
- 代码从上至下开始执行
- promise 状态变为 resolve,return 1
- 执行 then
- console.log(res) res –> return new Promise, resolve(1) 加入微任务队列
- promise 状态变为 resolve, return 2
- 执行 finally
- console.log(‘finally2’)
- return new Promise, resolve(‘我是finally2返回的值’) 加入微任务队列
- 执行 finally
- 当前同步代码执行完成,开始还行微任务队列
- console.log(‘finally’)
- console.log(‘finally2后面的then函数’, res) res –> 2
结论
- .finally()方法不管Promise对象最后的状态如何都会执行
- .finally()方法的回调函数不接受任何的参数,也就是说你在.finally()函数中是没法知道Promise最终的状态是resolved还是rejected的
- 它最终返回的默认会是一个上一次的Promise对象值,不过如果抛出的是一个异常则返回异常的Promise对象。
- promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用
- 运行结果
Promise 中的 all 和 race
- Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败 promise 的结果。
- Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
4.1 题目一
1 | function runAsync (x) { |
分析
- 代码从上至下开始执行
- Promise.all 入队微任务队列
- 当前同步代码执行完成,开始执行微任务队列
- 间隔一秒后,控制台会同时打印出1, 2, 3,还有一个数组[1, 2, 3]
结论
- 有了all,就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据
- .all()后面的.then()里的回调函数接收的就是所有异步操作的结果。
- 而且这个结果中数组的顺序和Promise.all()接收到的数组顺序一致
运行结果
4.2 题目二
1 | function runAsync (x) { |
分析
- 代码从上至下开始执行
- Promise.all 入队微任务队列
- 当前同步代码执行完成,开始执行微任务队列
- 间隔一秒后,控制台会同时打印出 1, 3
- 由于 runReject(4) 比 runReject(2) 晚入微任务队列,且 catch 只执行一次
- 再间隔一秒后
- console.log(x) x –> 2
- console.log(‘Error: 2’)
- 再间隔2秒后
- console.log(x) x –> 4
- 不会再执行 catch
- 再间隔一秒后
运行结果
等同于
1
2
3
4
5
6
7
8
9
10
11function runAsync (x) {
const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))
return p
}
function runReject (x) {
const p = new Promise((res, rej) => setTimeout(() => rej(`Error: ${x}`, console.log(x)), 1000 * x))
return p
}
Promise.all([runAsync(1), runReject(4), runAsync(3), runReject(2)])
.then(res => console.log(res),
err => console.log(err);结论
- all 和 race传入的数组中如果有会抛出异常的异步任务,那么只有最先抛出的错误会被捕获,并且是被 then 的第二个参数或者后面的 catch 捕获;但并不会影响数组中其它的异步任务的执行。
4.3 题目三
1 | function runAsync (x) { |
分析
- 代码从上至下开始执行
- Promise.race 入队微任务队列
- 当前宏任务中同步任务执行完成,开始执行微任务
- 在间隔时间都一样的情况下,runAsync1 最先加入队列
- 因此
- console.log(x) x –> 1
- 由于 race 的特殊性,只捕捉最先执行完成的那个结果
- console.log(‘result: ‘, res) res –> 1
- 再
- console.log(x) x –> 2
- console.log(x) x –> 3
- 没有报错,不会被 catch
运行结果
4.4 题目四
1 | function runAsync(x) { |
分析
- 代码从上至下开始执行
- Promise.race 入队微任务队列
- 当前宏任务中同步任务执行完成,开始执行微任务
- runReject(0) 最先传入
- 因此
- console.log(x) x –> 0
- console.log(err) err –> Error: 0
- 间隔小于1秒后
- runAsync(1), runAsync(2), runAsync(3)
- 依次为
- console.log(x) x –> 1
- console.log(x) x –> 2
- console.log(x) x –> 3
运行结果
参考文献
写在后面
- 祝大家多多发财