5.11 catch方法-异常穿透与值传递
catch 方法是获取失败的值,因为前面 then() 方法 已经写的很完善了,所以 catch 只要调用一下 then() 就好
index.html
let p = new Promise((resolve, reject) => { setTimeout(() => { reject('Err'); }, 1000) }) let res = p.catch(reason => { console.log(reason); return 321; }) console.log(res);
promise.js
Promise.prototype.catch = function (onRejected) { return this.then(undefined, onRejected); }
接下来就是要完成 catch 方法的异常穿透效果
我按照顺序写下来:
index.html
let p = new Promise((resolve, reject) => { setTimeout(() => { reject('Err'); }, 1000) }) let res = p.then(value => { console.log(111); }).then(value => { console.log(222); }).then(value => { console.log(333); }).catch(reason => { console.log(reason); })
这时候会出现这个问题:
说 onRejected 方法不存在,这是什么情况?
解释:
executor 执行器在执行它内部异步代码前,同步代码已经执行结束了,也就是 p.then() 执行完毕,把 then 内的回调方法存到了 p 的自身属性上。而 then 的回调参数有两个:onResolved,onRejected。在这个案例中只传了一个 onResolve,没有onRejected,所以 保存在 p 本身上的回调函数 onRejected 就为空。所以我们要主动给 then 方法内加上一个 onRejected 回调函数。
Promise.prototype.then = function (onResolved, onRejected) { // 判断回调函数的参数 // then 方法中并没有 onRejected 这个回调方法 if (typeof onRejected !== 'function') { // 手动给 then 添加这个回调函数 onRejected = reason => { // 抛异常 throw reason; } } }
这时候在最后的 catch 就能接受到这个异常了
值传递的话就是 then 方法连 onResolved 回调函数都不传递了。
let p = new Promise((resolve, reject) => { setTimeout(() => { reject('Err'); }, 1000) }) let res = p.then().then(value => { console.log(222); }).then(value => { console.log(333); }).catch(reason => { console.log(reason); })
这时候处理方法和上面的类似,加一个 onResolved 回调就行
// then 方法中并没有 onResolved 这个回调方法 if (typeof onResolved !== 'function') { // 手动给 then 添加这个回调函数 onResolved = value => { return value; } }
5.12 resolve 方法封装
比较简单直接亮代码
Promise.resolve = function (value) { // 返回 promise 对象 return new Promise((resolve, reject) => { if (value instanceof Promise) { value.then(v => { resolve(v); }, r => { reject(r); }) } else { resolve(value); } }) }
.13 all 方法封装
index.html
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Ok') }, 1000) }) let p2 = new Promise((resolve, reject) => { resolve('success') }); let p3 = new Promise((resolve, reject) => { resolve('dddd') }); let res = Promise.all([p1, p2, p3]) console.log(res);
promise.js
Promise.all = function (promises) { return new Promise((resolve, reject) => { // 声明变量 let count = 0; let arr = []; // 遍历 for (let i = 0; i < promises.length; i++) { promises[i].then(v => { // 对象的状态是成功 count++; // 将当前 promise 对象成功的结果 存到数组中 arr[i] = v; // 判断如果全都成功,就返回成功 if (count === promises.length) { // 修改状态 resolve(arr); } }, r => { reject(r); }) } }) }
5.14 then 方法 回调的异步执行
先看场景:
index.html
let p1 = new Promise((resolve, reject) => { // setTimeout(() => { // resolve('Ok') // }, 1000) resolve('OK') console.log(111); }) p1.then(value => { console.log(222); }) console.log(333);
要求我们的 then 内部的回调方法应该是异步执行。
打印结果:却是同步执行
用一个比较粗糙的方法解决,给所有的回调函数加上定时器:
总结分析:
这个尚硅谷的视频一直没讲链式调用的过程,我觉得是一个很大的遗憾,我这一块还是挺迷糊的,我尝试着自己来分析一下。
首先我们需要知道 then 本身是同步,只是它内部的回调函数是异步的。可以用代码来测试一下
index.html
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Ok') }, 1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000) }); let p3 = new Promise((resolve, reject) => { resolve('dddd') }); p1.then(value => { console.log(value); console.log(111); return p2; }).then(value => { console.log(value); console.log(222); return p3; }).then(value => { console.log(value); console.log(333); }) console.log('我得在回调之前执行');
promise.js
修改 then 函数内容,在调用 then 时一上来就打印一下。
Promise.prototype.then = function (onResolved, onRejected) { console.log('then本身是同步的'); }
我们可以看一下打印结果:
可以得出结论,then 的链式调用会在一开始就一下子执行下去。then 内部的回调函数是按照我们写的顺序执行下去,为啥会这样呢,我们一行一个代码逐步解析一下。
先分析一上来定义出来的三个 promise 对象
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Ok') }, 1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000) }); let p3 = new Promise((resolve, reject) => { resolve('dddd') });
第一步:定义 P1 是一个 promise 对象,P1 的 PromiseState 是 pending,PromiseResult 是 null,this.callback = []。然后开始调用 executor(resolve, reject) 执行器, 但执行器内部一进去就是一个定时器,定时器是异步函数调用,进异步队列,然后继续往下执行就到 P2。
第二步:定义 P2,P2 与 P1 类似, PromiseState 和 PromiseResult 都一样,executor 执行器内部也是一个定时器,进异步队列
第三步:定义 P3,因为 P3 executor 执行器 内部没有异步函数,所以直接按照同步代码执行,而且调用的是 resolve,所以返回的是成功 PromiseState 为 fulfilled, PromiseResult 为 Ok ,callback 是 异步函数,虽然里面还没有值,还是要放到异步队列中。
开始比较刺激的 then 方法调用了。
第四步:P1 调用 then 方法,这个过程是这样的,因为我们之前在 then 的第一行添加了console.log('then本身是同步的'); ,所以控制台打印 then本身就是同步的。 接着因为 P1 的 PromiseState 是 pending ,所以会将 then 中的回调函数保存到 P1 的 callback 属性上。这时 P1.then() 方法执行结束,返回一个 Promise 对象,我把该返回的 Promise 对象命名为 X。该Promise 对象正处于 pending 状态中。
第五步:链式调用 then。
这个就相当于 X 在调用 then
X 此时 是 pending 状态,调用 then 就是 先打印下 then本身是同步的 这句话,然后就是 把 then 内部的回调函数保存 X 的 callback 上。这时, X.then() 执行结束, X.then() 返回一个对象,我称之为 Y ,这个 Y 对象状态此时也是 pending。
第六步:最后一个链式调用
p1.then(value => { console.log(value); console.log(111); return p2; }).then(value => { console.log(value); console.log(222); return p3; }).then(value => { console.log(value); console.log(333); })
分解后相当于:
let X = p1.then(value => { console.log(value); console.log(111); return p2; }) let Y = X.then(value => { console.log(value); console.log(222); return p3; }) let Z = Y.then(value => { console.log(value); console.log(333); })
就和上面一样,Y 此时是 pending 状态,所以调用 then 方法,直接将回调函数保存到 Y 的 callback 属性上,返回一个新 Promise ,状态是 pending。
这是同步代码执行完,控制台打印的结果:
到此为止,所有同步代码执行完毕,开始执行异步代码,这一部分又是老大难。。
第七步:开始执行异步队列的代码
异步队列里有三个异步函数, 其实 setTimeout-回调-P3 这个异步函数并没有作用,因为 P3 的 callback 是空的。有作用的就 setTimeout-P1 和 setTimeout-P2。
因为只有 P1.then() 调用了,存有属于自己的回调函数,别的 P2,P3 都没有 .then() 拥有自己的回调函数。所以就算 P2 或 P3 的执行器里的 异步函数先完成,他们的 callback 也是空的,没有回调函数可以调用。
接下来直接讲解下这个调用过程。
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Ok') }, 1000) }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 2000) }); let p3 = new Promise((resolve, reject) => { resolve('dddd') }); let X = p1.then(value => { console.log(value); console.log(111); return p2; }) let Y = X.then(value => { console.log(value); console.log(222); return p3; }) let Z = Y.then(value => { console.log(value); console.log(333); }) console.log('X', X); console.log('Y', Y); console.log('Z', Z); console.log('P1', p1); console.log('P2', p2); console.log('P3', p3);
还没执行前所有对象的状态
我们来看一下执行的步骤过程。
第一步:先执行完 P1 的 resolve 方法(P1 异步函数在执行时会记住 P1 的环境),将 P1 的属性都成功更改为 fulfilled 和 Ok,然后开始执行 P1 callback 里保存的回调函数。
这个回调函数是
这个回调时被修饰过的,修饰后:
执行后结果是,打印 value,打印 111,将 P2 返回给 res
P1.then() 执行结束时返回了一个 Promise 对象给 X。而 res 执行的环境是Promise X 的环境
resolve 执行就是修改 X 的状态,X 更改为 fulfilled,内容为 success ,然后要执行 X 的 callback 里的回调函数。X callback里的回调函数是 X.then() 时保存的。
然后就和上面的过程一样。
最终结果:一切都顺利执行。
然后就和上面的过程一样。
最终结果:一切都顺利执行。
本质:
尚硅谷的 promise 的实现研究后发现就是在输出结果上模拟了真实 promise 的实现过程。但实现过程不是真的 promise。如果存在这么一个场景,p2 的执行需要用到 p1 完成后的结果,那尚硅谷模拟的 promise 是无法完成的。因为 尚硅谷的 promise 只是把 p1,p2,p3的结果按顺序打印出来而已,实际上三者的异步方法在执行过程中是无关的。
6. Promisification
指将一个接受回调的函数转换为一个返回 promise 的函数。
由于许多函数和库都是基于回调的,因此,在实际开发中经常会需要进行这种转换。因为使用 promise 更加方便,所以将基于回调的函数和库 promisify 是有意义的。(译注:promisify 即指 promise 化)
具体案例可以看 2.3 2.4 的练习
7. 简介 async await
7.1 async
- 函数的返回值为 promise 对象。
- promise 对象的结果由 async 函数执行的返回值决定
7.2 await
- await 右侧的表达式一般为 promise 对象,但也可以是其他的值
- 如果表达式 promise 对象。await 返回的是 promise 成功的值
- 如果表达式是其它值,直接将此值作为await 的值
7.3 实际案例1
const fs = require('fs') const util = require('util') // promisify 处理完返回的是一个 promise 对象 const mineReadFile = util.promisify(fs.readFile); async function main() { // 读取第一个文件的内容 let data1 = await mineReadFile('./a.txt'); let data2 = await mineReadFile('./b.txt'); let data3 = await mineReadFile('./c.txt'); }
7.4 实际案例2
function sendAJAX(url) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.send(); // 处理结果 xhr.onreadystatechange = function () { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.status) } } } }) } let btn = document.querySelector('#btn'); btn.addEventListener('click',async function () { // 获取信息 let message = await sendAJAX('https://api.apiopen.top/getJoke'); console.log(message); })