异步非全解 期约Promise
大纲链接 §
[toc]
回调地狱 ⇧
避免回调地狱的原因 ⇧
- 多层回调函数的相互嵌套,就形成回调地狱
例如嵌套网络请求,第一次网络请求成功后回调函数再次发送网络请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17getCityFromIp(ip, (city) => { getWeatherFromCity(city, (weather) => { getSuggestionFromWeather(weather, suggestion => { ... }) }) }) setTimeout(() => { console.log('延时1秒后输出') setTimeout(() => { console.log('延时2秒后输出') setTimeout(() => { console.log('延时3秒后输出') }, 3000) }, 2000) }, 1000)层层嵌套,难以维护
冗余代码嵌套层级过深,可读性差
错误处理更复杂
为了代码更加具有 可读性 和 可维护性 ,需要将 数据请求 与 数据处理 明确的区分开来,ES6中使用 Promise 解决回调地狱
- 分离 数据请求 操作与 数据处理 操作
- 将层层嵌套变为
.then()链式调用,减少缩进 - 使用写同步的方式写异步
易于错误处理
1 2 3 4f1(a) .then(b => f2(b), onerror1) .then(c => f3(c), onerror2) .catch(err => {})
如何确保代码执行顺序
可以利用函数调用栈,将想要执行的代码放入 回调函数 中
1 2 3 4 5 6 7 8 9 10 11 12 13// 一个简单的封装 function wantFn() { console.log('执行要最后执行的代码'); } function fn(wantFn) { console.log('这里执行了一大堆各种代码'); // 其他代码执行完毕,最后执行 回调函数 wantFn && wantFn(); } fn(wantFn);
除了利用函数调用栈的执行顺序之外,还可以利用 队列机制
|
|
利用 Promise 将任务放在任务队列中
|
|
Promise的基本概念 ⇧
Promise是一个特殊的 对象类型,用来 处理异步操作- 确切地说是 封装一个异步操作并获取结果
Promise是一个构造函数new Promise()先返回一个 pending 状态的 promise 实例对象
创建
Promise实例:const promise = new Promise(executor);- 这个执行函数
executor代表 异步操作 - 参数
executor的类型为函数 - 还需向
executor提供两个类型为函数的参数(resolve, reject) => { /*一般写异步函数*/}resolve(result)成功时的回调- 把返回的该Promise对象的状态从变 pending 为
fulfilled - 并在该Promise对象中传递(包裹)成功的结果
result
- 把返回的该Promise对象的状态从变 pending 为
reject(reason)失败时的回调- 把返回的该Promise对象的状态从变 pending 为
rejected - 并在该Promise对象中传递(包裹)失败的信息
reason
- 把返回的该Promise对象的状态从变 pending 为
参数
executor函数是自动执行- 但必须在函数内部手动调用
resolve(result)/reject(reason),才会改变返回的 Promise 对象的结果 否则 返回的 Promise 对象一直处于 pending 状态
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// executor为一个普通函数 方便在面试题中记录命名 一般用箭头函数 new Promise(function fn() {}) // Promise {<pending>} // executor为一个匿名的箭头函数 new Promise(() => {}) // Promise {<pending>} // 提供两个类型为函数的参数,命名任意,但约定俗成为 resolve 和 reject new Promise((resolve, reject) => {}) // Promise {<pending>} // 在executor中调用 resolve() new Promise((resolve, reject) => {resolve()}) // Promise {<fulfilled>: undefined} // 在executor中调用 reject() new Promise((resolve, reject) => {reject()}) // Promise {<rejected>: undefined} // resolve 一般必须写出 reject可省略 调用reject必须先写出resolve new Promise((resolve/*, reject*/) => {resolve()}) // Promise {<fulfilled>: undefined} // 传值 通过返回一个新的包含值的Promise对象来传值 new Promise((resolve) => {resolve(1)}) // Promise {<fulfilled>: 1} // 传失败的信息 通过返回一个新的包含失败信息的Promise对象来传失败的信息 new Promise((resolve, reject) => {reject(1)}) // Promise {<rejected>: 1} // 以上都是同步执行 // 以下异步处理 new Promise((resolve, reject) => { // 定时器 API setTimeout(() => { resolve() }, 0) }) /* 一开始同步执行 返回一个 pending 状态的Promise对象; 异步代码执行,将该Promise对象的状态变为 fulfilled */ new Promise((resolve, reject) => { // 定时器 API setTimeout(() => { reject() }, 0) }) /* 一开始同步执行 返回一个 pending 状态的Promise对象; 异步代码执行,将该Promise对象的状态变为 rejected */
- 但必须在函数内部手动调用
- 这个执行函数
promise 实例对象创建与使用过程
new Promise((resolveCb, rejectCb) => {/*一般写异步函数*/}),预先声明的形参resolveCb、rejectCb- 先返回一个
pending状态的 promise 实例对象- 如果走成功的逻辑,调用
resolveCb(传值)- 立即把该Promise对象的状态变为
fulfilled
- 立即把该Promise对象的状态变为
- 如果走失败的逻辑,调用
rejectCb(传失败原因)- 立即把该Promise对象的状态变为
rejected
- 立即把该Promise对象的状态变为
- 如果走成功的逻辑,调用
该 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// 1. 新建一个 promise对象 // 2. new 一个构造函数 Promise,接收一个回调函数,这个函数被称为 执行器函数,里面一般执行异步任务 // 3. 执行器函数接收两个参数 resolved和rejected 两个参数都为函数类型 // 4. 异步操作根据逻辑条件判断,成功 执行resolved(result);失败 执行rejected(reason) const p = new Promise((resolved, rejected) => { setTimeout(() => { // 模拟异步任务 如果当前时间为偶数就成功 否则就失败 const time = Date.now() time % 2 === 0 ? resolved(`执行成功 ${time}`) : rejected(`执行失败 ${time}`) }, 1000) }) p.then( // 接收成功的value数据 onResoled value => { console.log(`onResoled ${value}`) }, // 接收失败的reason数据 onRejected reason => { console.log(`onRejected ${reason}`) } )
- 先返回一个
promise 实例对象内部运行变化
- 当
new Promise()被实例化后,即表示 Promise 进入 pending 初始化状态,准备就绪,等待后续调用方法 - 一旦 promise 实例运行成功或者失败之后,实例状态就会变为 fulfilled 或者 rejected
- 此时状态就无法变更
- 内部主要就是状态的变化
- 在状态为 pending 时,会等待,不将回调放入相应的队列(宏/微)中
- 一旦对象状态改变成非 pending 态,就会将回调放入相应的队列中
处理非异步情况:
|
|
- 先实例化 Promise,同时执行完执行器函数(同步),状态由 pending 变为 fulfilled,Promise 实例回调函数执行完成
- 此时并不会将 then 回调函数保存,函数顺序执行
- 继续执行,保存 then 回调,发现 Promise 状态已经变为 fulfilled,then 的成功回调直接运行
- 以上代码的两个then回调都是这样
处理异步情况:
|
|
- 先实例化 Promise,同时执行完执行器函数(同步)
- 由于是执行 setTimeout 函数,定时器开始计时,状态依然还是 pending
- Promise 实例的执行器函数执行完成,继续执行其他同步代码
- 执行第一个
p.then- Promise 状态还是 pending,并不会将 then 回调函数保存到任务队列中
- 执行第二个
p.then- Promise 状态还是 pending,并不会将 then 回调函数保存到任务队列中
- 执行栈代码清空,同步代码执行完毕
- 定时器到时,回调函数进入任务队列,状态依然还是 pending
- 开始抓取任务队列中的回调函数到执行栈中执行
console.log('setTimeout')打印setTimeoutresolve('Promise')立即将 状态变为fulfilled- 触发第一个
p.then将回调函数放到任务队列中 - 紧接着触发第二个
p.then将回调函数放到任务队列中 - 执行栈代码清空,开始抓取任务队列中的回调函数到执行栈中执行
- 执行被保存在任务队列中的 then 回调
console.log(1)打印1console.log(2)打印2
- 执行栈代码清空,所有代码执行完毕
提示:
then/catch都是一个 Promise
使用 Promise 的一般写法 ⇧
|
|
使用 Promise 前:
|
|
使用 Promise 后:
|
|
复杂案例:
|
|
在以下例子中,假设
getUserCount是一个返回Promise的函数
|
|
- 如果尝试立即显示
userCount变量的值,会得到的是Promise {<pending>} - 这是有可能发生的,因为还没有数据,需要等待它的返回值
- 在不能立即获得返回值的情况下,许多异步函数会返回一个
Promise
其他基础概念
Promise的执行过程
- 初始化 Promise 状态为(Pending)
- 立即同步执行 Promise 中传入的 fn 函数,将 Promise 内部
resolve、reject函数作为参数传给 fn,按事件机制执行机制或条件调用resolve(result: unknown)、reject(reason: unknown); - 执行器 fn 函数体中代码 如果没有执行
resolve()或reject(),则状态一直为pending,后续的.then返回的 Promise 状态也为 pending,不会将回调放入任务队列 - 如果同步执行了
resolve()或reject()则立即将状态变为相应的fulfilled或rejected - 如果异步异步执行
resolve()或reject(),状态仍为pending,等待执行异步回调,将状态变为fulfilled或rejected - 状态一变化,就分别传递成功或失败的结果
resolve(result: unknown)、reject(reason: unknown)给下一步 - 执行
.then()注册回调到微任务队列,分别接收上一步成功或失败的结果resolve(result: unknown)或reject(reason: unknown) - Promise 的关键是要保证
.then()方法传入的参数onFulfilled和onRejected,必须在.then()方法被调用的那一轮事件循环之后的新执行栈中执行
链式 Promise 是指在当前 promise 达到
fulfilled或rejected状态后,即开始进行下一个 promise
Promise的状态 ⇧
一个 Promise 可以有多种状态:
- 进行中(Pending) - 响应还没有就绪。请等待
- 比如正在进行的网络请求还未响应,或者定时器还没有到时间
- 已完成(Fulfilled) - 响应已完成。成功。请获取数据
- 当主动调用 resolve() 函数,就处于满足状态,并会回调 then()
- 已拒绝(Rejected) - 出现了一个错误。请处理它
- 当主动调用 reject() 函数,就处于该状态,并且会回调 catch()
当在 pending 进行中 的状态时,不可以做任何事,仅仅是等待
new Promise((resolveCb, rejectCb) => {})- 调用函数
resolveCb()Promise对象由pending状态变为fulfilled状态 - 调用函数
rejectCb()Promise对象由pending状态变为rejected状态
- 调用函数
只有变为非 pending 状态,才会执行 .then 中相应的回调
|
|
状态转换只有两种
pending -> resolvedpending -> rejected
不显示写明返回值,默认返回 undefined
|
|
一个 promise 对象的状态只能改变一次状态
- 无法取消,无法回退
一般地,成功后结果数据为
value失败后结果为reason1 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//Question3 new Promise((resolve, reject) => { resolve(1) // 状态成功,往下执行 reject() // 状态已变化,忽略本行代码 }) .then(function(res) { // 接收上一次传来的结果 1 console.log('promise1') return res // 将上一次传来的结果 1,隐式调用 Promise.resolve(1) 封装为Promise对象 }) .then(function(res) { console.log(res) // 接受上一步的返回值 }) // promise1 // 1 new Promise((resolve, reject) => { reject(1) // 状态失败,往下执行 resolve(1) // 状态已变化,忽略本行代码 }) .then(function(res) { // 忽略成功的回调 console.log('promise1') return res }, function(reason) { // 走失败回调的逻辑 console.log(reason) // 输出1 return reason // 返回 Promise {<fulfilled>: 1} }) .then(function(res) { console.log(res) // 输出1 }) // 返回 Promise {<fulfilled>: undefined} // 1 // 1
.then(1)传参不为函数时,直接忽略此步
|
|
.then()
.then()为了后续处理成功/失败接收报错信息
根据 接收上一个Promise 对象实例中处理的 状态为成功或失败,分别调用
resolve()和reject()1 2 3 4 5 6 7 8 9 10 11const userCount = getUserCount(); // 成功 const handleSuccess = (result) => { console.log(`Promise was fulfilled. Result is ${result}`); } userCount.then(handleSuccess); // 失败 const handleReject = (error) => { console.log(`Promise was rejected. The error is ${error}`); } userCount.catch(handleReject);getUserCount 函数返回一个 Promise,所以不能直接使用 userCount
为了有效处理返回的数据(data),需要增加
.then和.catch的处理函数,以便成功或失败的时候可以被调用.then会接收上一个任务的返回值,如果.then中的操作没有意义会被忽略
then 和 catch 函数可以被连续的调用
在这个例子中,同时关注成功(success)和失败(failure)
1 2 3 4 5 6 7 8 9 10 11const userCount = getUserCount(); const handleSuccess = (result) => { console.log(`Promise was fulfilled. Result is ${result}`); } const handleReject = (error) => { console.log(`Promise was rejected. The error is ${error}`); } userCount.then(handleSuccess).catch(handleReject);Promise.prototype上包含一个.then()方法- 控制台展开打印
console.dir(Promise) Promise实例对象可以通过原型链的方式访问到.then()方法p.then()
- 控制台展开打印
.then()方法(的参数)用来预先指定成功和失败的回调函数- 异步操作都为耗时操作,存在两种结果成功或失败
p.then(成功的回调函数, 失败的回调函数)p.then(result => {}, error => {})- 调用
.then()方法时,成功的回调函数是必选的,失败的回调函数是可选的
一个抛硬币的示例
|
|
.then()链式调用 ⇧
一个异步操作在另一个异步操作之后执行的场景,需要处理 Promise chain 期约链
|
|
抛硬币的示例
|
|
betAgain函数接收一个数字,并且展示成功消息,然后再次调用coinFlip函数
如果只关心最后的结果而忽略过程,在
.then()方法中只需传递coinFlip函数
|
|
- 调用
resolve(resultValue)就能跳转到 then() 方法就能执行处理代码 then(resolveCb, rejectCb)回调的返回值又是一个Promise对象- 只要是.then() 必然就是执行处理代码
- 如果还有嵌套必然就是返回一个 Promise 对象
.then((result1)=>{..return result2}, (reason)=>{}).then((result2)=>{}, ...)形成链式调用- 链式调用就是
.then()方法的返回值返回一个Promise对象继续调用.then() - 此外还有手动包装成
Promise对象的方法:Promise.resolve(),可省略,因为链式调用
区别 当立即
Promise.resolve()返回fulfilled状态的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 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// 运行 node src/promise/promise-then-chain.js let p1 = Promise.resolve() // sync code .then(() => console.log(1) /*return undefined*/) // .resolve(undefined) // Promise {<fulfilled>: undefined} .then(() => console.log(2)) // Promise {<pending>} .then(() => console.log(3)) // Promise {<pending>} p1.then(() => console.log(4)) // Promise {<fulfilled>: undefined} .then(....) p1.then(() => console.log(5)) let p11 = Promise.resolve() // sync code .then(() => console.log(11)) // Promise {<fulfilled>: undefined} .then(() => console.log(22)) // Promise {<pending>} .then(() => console.log(33)) // Promise {<pending>} p11.then(() => console.log(44)) p11.then(() => console.log(55)) /* - sync code - Promise.resolve() // 返回一个 状态为 fulfilled 的 Promise 对象,可记为pm1 - pm1.then(() => console.log(1)) // pm1 状态为 fulfilled,所以将 () => console.log(1) 添加到微队列,记为f1,返回一个状态为 pending 的新Promise对象,可记为pm2;此时微队列[f1] - pm2.then(() => console.log(2)) // pm2 状态为 pending,所以 () => console.log(2) 未添加到微队列,记为f2,返回一个状态为 pending 的新Promise对象,可记为pm3;此时微队列[f1] - pm3.then(() => console.log(3)) // pm3 状态为 pending,所以 () => console.log(3) 未添加到微队列,记为f3,返回一个状态为 pending 的新Promise对象,赋值给p1;此时微队列[f1] - p1.then(() => console.log(4)) // p1 状态为 pending,所以 () => console.log(4) 未添加到微队列,记为f4,返回一个状态为 pending 的新Promise对象,未赋值任何变量;此时微队列[f1] - p1.then(() => console.log(5)) // p1 状态为 pending,所以 () => console.log(5) 未添加到微队列,记为f5,返回一个状态为 pending 的新Promise对象,未赋值任何变量;此时微队列[f1] - Promise.resolve() // 返回一个 状态为 fulfilled 的 Promise 对象,可记为pm11 - pm11.then(() => console.log(11)) // pm11 状态为 fulfilled,所以 () => console.log(11) 添加到微队列,记为f11,返回一个状态为 pending 的新Promise对象,可记为pm22;此时微队列[f1, f11] - pm22.then(() => console.log(22)) // pm22 状态为 pending,所以 () => console.log(22) 未添加到微队列,记为f22,返回一个状态为 pending 的新Promise对象,可记为pm33;此时微队列[f1, f11] - pm33.then(() => console.log(33)) // pm33 状态为 pending,所以 () => console.log(33) 未添加到微队列,记为f33,返回一个状态为 pending 的新Promise对象,赋值给p11;此时微队列[f1, f11] - p11.then(() => console.log(44)) // p11 状态为 pending,所以 () => console.log(44) 未添加到微队列,记为f44,返回一个状态为 pending 的新Promise对象,未赋值任何变量;此时微队列[f1, f11] - p11.then(() => console.log(55)) // p11 状态为 pending,所以 () => console.log(55) 未添加到微队列,记为f55,返回一个状态为 pending 的新Promise对象,未赋值任何变量;此时微队列[f1, f11] - 执行栈为空,同步代码执行完毕,扫描微队列[f1, f11] - 抓取 微队列[f1, f11]中的任务f1到执行栈 - 执行f1,输出1 - 将pm2的状态变为 fulfilled,触发将 f2 添加到微队列[f11, f2] - 执行栈为空,同步代码执行完毕,扫描微队列[f11, f2] - 抓取 微队列[f11, f2]中的任务f11到执行栈 - 执行f11,输出11 - 将pm22的状态变为 fulfilled,触发将 f22 添加到微队列[f2, f22] - 执行栈为空,同步代码执行完毕,扫描微队列[f2, f22] - 抓取 微队列[f2, f22]中的任务f2到执行栈 - 执行f2,输出2 - 将pm3的状态变为 fulfilled,触发将 f3 添加到微队列[f22, f3] - 执行栈为空,同步代码执行完毕,扫描微队列[f22, f3] - 抓取 微队列[f22, f3]中的任务f22到执行栈 - 执行f22,输出22 - 将pm33的状态变为 fulfilled,触发将 f33 添加到微队列[f3, f33] - 执行栈为空,同步代码执行完毕,扫描微队列[f3, f33] - 抓取 微队列[f3, f33]中的任务f3到执行栈 - 执行f3,输出3 - 将p1的状态变为 fulfilled,触发 p1.then(f4) 将 f4 添加到微队列[f33, f4] - p1的状态为 fulfilled,触发 p1.then(f5) 将 f5 添加到微队列[f33, f4, f5] - 执行栈为空,同步代码执行完毕,扫描微队列[f33, f4, f5] - 抓取 微队列[f33, f4, f5]中的任务f33到执行栈 - 执行f33,输出33 - 将p11的状态变为 fulfilled,触发 p11.then(f44) 将 f44 添加到微队列[f4, f5, f44] - p11的状态变 fulfilled,触发 p11.then(f55) 将 f55 添加到微队列[f4, f5, f44, f55] - 执行栈为空,同步代码执行完毕,扫描微队列[f4, f5, f44, f55] - 抓取 微队列[f4, f5, f44, f55]中的任务f4到执行栈 - 执行f4,输出4 - 抓取 微队列[f5, f44, f55]中的任务f5到执行栈 - 执行f5,输出5 - 抓取 微队列[f44, f55]中的任务f44到执行栈 - 执行f44,输出44 - 抓取 微队列[f55]中的任务f55到执行栈 - 执行f55,输出55 - 执行栈为空,微队列为空,宏队列为空,全部代码执行完毕 * 连续链式调用 .then().then() 时 的运行机制,为同步执行 * 分开链式调用 p.then(fn) 时 的运行机制,为同步执行 * 交替使用分开的(有中间值的)链式调用 p1.then(fn); p2.then(fn) 时 的运行机制,为同步执行 * 仅当promise状态不为pending,才将对应回调放入微队列 * 状态为fulfilled时,添加fn到微队列;未被添加对微队列是因为状态仍为pending,继续等待状态变化 * * */公理1: 只有当前
Promise对象resolve后,才会触发让其后的.then(fn)中的fn加入微队列,否则当前Promise对象会一直处于Pending状态- 即 对于一个处于
Pending状态的Promise对象实例p,只有当内部状态resolved,才会让p.then(fn)中的fn加入微队列
- 即 对于一个处于
公理2:(上一个
Promise对象状态fulfilled)执行语句时,默认return的是undefined,会导致当前Promise对象自动resolve(undefined)返回Promise {<fulfilled>: undefined}
调整代码,验证
|
|
- 一个
.then()就相当于一层回调嵌套,就是将 后续所有链式代码 作为 一整个微任务加入微队列,只不过代码形式上是 链式调用,而执行的逻辑仍然是嵌套执行 - 分开链式调用
p.then(fn)是否将fn立即放入微队列取决于p的状态是否从pending变为fulfilled或rejected- 如果仍是
pending,就继续等待,暂不放入微队列 - 如果已有结果,则根据不同结果执行回调逻辑
- 如果仍是
异步操作-宏任务分析
|
|
- f1 是同步执行代码
- 执行代码 创建一个定时器,开始计时,f2还未加入宏队列[]
- 返回一个 pending 状态的 Promise 对象,赋值给p
- 执行下一句 p ,由于状态为 pending,
p.then中的回调 不执行- f3 没有立即加入微任务队列
- 同步代码执行完毕,执行栈[],微队列[],宏队列[]
- 扫描微队列,空,扫描宏队列,空
- 1秒后 计时器到时计时完毕,将f2放入宏队列[f2]
- 抓取微任务队列中的任务(为空)到执行栈
- 再扫描宏队列(抓取一个宏任务 f2 到执行栈),执行栈[f2],宏队列[]
- 执行f2
- 执行
resolve(2),把Promise的状态从pending变为fulfilled- 返回一个包装成Promise对象的值2
- 传递值到下一个
.then(resolve(2))
- 触发
p.then(f3), 将 f3 移入微队列[f3] - 继续 执行
console.log(1),输出1 - 执行栈为空,抓取微任务队列中的任务[f3]到执行栈,执行栈[f3]
- 执行
- 执行f3,接收
.then(f3(2))传值2,输出2 - 此时执行栈为空,微队列[],宏队列[]
- 全部代码执行完毕
实例:异步宏任务+交替使用有中间值的链式调用分析
|
|
链式调用
.then之后会返回一个新的Promise对象
- 执行同步的代码
- 运行f1,加入宏队列[resolve1](f1里面的resolve1函数被加入宏队列,还没开始执行)
- 立即得到一个pending状态的Promise对象,赋值给p1
- 运行p1.then,得到一个pending状态的Promise对象,赋值给p2,未放入微队列
- 运行p2.then,得到一个pending状态的Promise对象,赋值给p3,未放入微队列
- 继续同步代码,同理,运行f11
- f11里面的resolve2函数被加入宏队列,还没开始执行,此时宏任务队列
[resolve1, resolve2] - 运行p11.then,得到一个pending状态的Promise对象,赋值给p22
- 运行p22.then,得到一个pending状态的Promise对象,赋值给p33
- f11里面的resolve2函数被加入宏队列,还没开始执行,此时宏任务队列
- 执行栈为空,同步代码执行完毕,此时微队列[],空,宏队列[resolve1, resolve2]
- 此时微队列为空,所以扫描宏队列,拿出resolve1到执行栈中运行
- p1被resolve(从pending变成fulfilled)
- 从而导致p1.then(f2)中的f2被加入微队列[f2]
- 执行栈为空,同步代码执行完毕,此时微队列[f2],宏队列[resolve2]
- 扫描全部微任务[f2],拿出f2到执行栈中运行,输出2
- f2运行结束(函数结束或者遇到return)时,触发p2内部状态的变化(p2从pending变成fulfilled)
- 导致p2.then(f3)中的f3加入微任务队列[f3]
- 执行栈为空,同步代码执行完毕,此时微队列[f3],宏队列[resolve2]
- 微任务队列[f3]不为空,拿出f3到执行栈中运行,输出3
- 此时p3变成fulfilled状态,微任务队列为[]
- 执行栈为空,同步代码执行完毕,此时微队列[],宏队列[resolve2]
- 扫描下一个宏任务,拿出resolve2到执行栈中运行
- 导致p11被resolve,从而导致p11.then(f22)中的f22被加入微队列[f22]
- 执行栈为空,同步代码执行完毕,此时微队列[f22],宏队列[]
- 扫描全部微任务,拿出f22运行,输出22
- f22运行结束时,触发p22从pending变成fulfilled,导致p22.then(f33)中的f33加入微队列[f33]
- 执行栈为空,同步代码执行完毕,此时微队列[f33],宏队列[]
- 微队列还未扫描完,拿出f33到执行栈中运行,输出33
- 此时p33变成fulfilled状态,微队列为[]
- 执行栈为空,同步代码执行完毕,此时微队列[],宏队列[]
- 全部代码执行完毕
以上代码等价于常见链式写法
|
|
对于一个处于
fulfilled状态的Promise对象p,p.then(fn)会立即让fn加入微任务队列
|
|
- 执行同步代码
p1 = Promise.resolve(1),创建一个内部状态为 fulfilled的Promise对象,赋值给p1p2 = p1.then(f2)- p1为fulfilled状态的Promise对象
- 立即让f2加入微任务队列(f2并未执行)
- 创建的p2是pending状态。此刻微队列为[f2]
p3 = p2.then(f3)- p2为pending状态的Promise对象
- 内部resolve才会让f3加入微队列
- 因为p2还没resolve,所以f3还没加微队列
p11 = new Promise(function f11(resolve){ resolve(11) })- 创建一个内部状态为fulfilled的Promise对象,赋值给p11
p22 = p11.then(f22)- p11为fulfilled状态的Promise对象
- 立即让f22加入微任务队列(f22并未执行)
- 创建的p22是pending状态。此刻微队列是[f2, f22]
p33 = p22.then(f33)- p22为pending状态的Promise对象
- 内部resolve才会让f33加入微队列
- 因为p22还没fulfilled,所以f33还没加微队列
- 同步代码执行完毕,执行栈[],微队列[f2, f22],宏队列[]
- 扫描微队列
- 拿出f2运行,输出2
- f2执行完时(函数结束或者遇到return),p2被resolve(变成fulfilled状态)
- 触发f3加入微队列。此刻微队列为[f22, f3]
- 拿出f22运行,输出22
- f22执行完时,p22被resolve
- 触发f33加入微队列。此刻微队列为[f3, f33]
- 拿出f3运行,输出3
- f3执行完,p3变成fulfilled状态
- 拿出f33运行,输出33
- f33执行完,p33变成fulfilled状态
- 拿出f2运行,输出2
- 全部代码执行完毕
以上代码等价于常见链式写法
|
|
Promise.prototype.then的应用场景
- 应用场景1:下个请求依赖上个请求的结果
- 应用场景2:中间件功能使用
应用场景1:下个请求依赖上个请求的结果
描述:类似微信小程序的登录
- 首先 执行微信小程序的 登录
wx.login,返回了code - 然后调用后端写的登录接口,传入
code - 然后返回
token - 然后每次的请求都必须携带
token 即下一次的请求依赖上一次请求返回的数据
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 37function A() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('B依赖的数据') }, 300) }) } function B(prams) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(prams + 'C依赖的数据') }, 500) }) } function C(prams) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(prams) }, 1000) }) } // 期望的是走 try // 由于A B C模拟的请求中都是没有reject // 用 try catch 捕获错误 try { A() .then(res => B(res)) .then(res => C(res)) .then(res => { console.log(res) // B依赖的数据C依赖的数据 }) } catch (e) { console.log(e) }
应用场景2:中间件功能使用
描述:接口返回的数据量比较大,在一个then 里面处理 显得臃肿,多个渲染数据分别给多个then,让其各司其职
|
|
Promise 实例中参数的调用机制 ⇧
new Promise((resolve, reject) => {})中的resolve和reject方法,在异步代码中调用
- 由于JS是单线程,会优先在主线程执行同步代码
- 异步代码会放到任务队列中
- 所以在刚运行时,Promise实例为 pending(进行中) 状态
- 等待主线程执行完毕,任务队列通知主线程,异步任务可以执行了,该任务才会进入主线程执行
- 此时setTimeout中的回调函数被调用,执行了resolve() 或 reject() 方法
- Promise实例会随之改变状态,并存储传入的数据
resolve()、reject()方法,不可重复调用
- 实例的一旦状态改变,就不会再变
- Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected
- 只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 settled(已定型)
由此可见 Promise 本质,就是一个状态机
- 当该 Promise 对象创建出来之后,其状态就是 pending(进行中)
- 然后通过程序来控制到底是执行已完成,还是执行已失败
- 因为 Promise 处理的是异步任务,当 Promise 的状态发生变化时,需要后续执行相应的函数(then/catch/finally)
Promise的缺点 ⇧
- 无法取消,一旦新建它就会立即执行,无法中途取消
- 不设置回调函数,Promise 内部抛出的错误,不会反应到外部
- 当处于 pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
AbortSignal API
- promise 一旦初始化,就不能中止
AbortSignal的出现使promise从语义上变为可中止的fetch API的AbortController接口已经集成了AbortSignal
解决方案参考
- AbortSignal promise
- 扩展 Promise 实现可取消、进度通知
- 面试官:如何中断已发出去的请求?
- 给你一个可以中断的Promise
- axios解析之cancelToken取消请求原理
异步操作之基于then-fs异步的读取文件内容 ⇧
创建三个文本
|
|
方式一:基于 回调函数 按顺序读取文件内容 ⇧
callback.js使用Node.js的文件读取模块fs.readFile()
|
|
- 使用命令行运行
node callback.js - 缺点明显是形成了回调地狱
- 注意如果报错
ReferenceError: fs is not defined- 说明在
package.json中设置了"type": "module",删除或改为"type": "commonjs" - 在前面加上
const fs = require('fs');
- 说明在
方式二:基于 then-fs 读取文件的内容 ⇧
由于
Node.js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持 Promise的调用方式
- 需要先安装第三方包:
then-fs,从而支持基于Promise的方式读取文件的内容 yarn add then-fs
导入并使用
then-fs读取文件
|
|
- 调用
then-fs提供的readFile()方法,可以异步地读取文件的内容 .readFile()的返回值是Promise的实例对象- 因此可以继续调用
.then()方法为每个Promise异步操作指定成功和失败之后的回调函数 - 各个
thenFs.readFile().then()之间无法保证文件的读取顺序,即乱序读取,相当于同时开启了三个异步操作 - 不能保证 精确地控制读取结果的顺序,需要进一步优化
方式三:基于Promise链式调用按顺序读取文件的内容 ⇧
.then()方法的特性
- 只有上一个
.then()中返回了一个新的Promise实例对象,才可以通过下一个.then()继续进行处理 通过
.then()方法的 链式调用,就解决了回调地狱的问题1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22import thenFs from 'then-fs' thenFs.readFile('./files/1.txt', 'utf8') // 1. 返回值是 Promise 的实例对象 .then(rt1 => { // 2. 通过 .then 为第一个 Promise 实例指定充公之后的回调函数 console.log(rt1) return thenFs.readFile('./files/2.txt', 'utf8') // 3. 在第一个 .then 中返回一个新的 Promise 实例对象 }) .then(rt2 => { // 4. 继续调用 .then 为上一个 .then 的返回值(新Promise实例)指定成功之后的回调函数 console.log(rt2) return thenFs.readFile('./files/3.txt', 'utf8') // 5. 在第二个 .then 中再返回一个新的 Promise 实例对象 }) .then(rt3 => { // 6. 继续调用 .then 为上一个 .then 的返回值(新Promise实例)指定成功之后的回调函数 console.log(rt3) }) /* // 折起代码可以更清楚地观察到以同步的形式进行异步操作 thenFs.readFile('./files/1.txt', 'utf8') .then(() => thenFs.readFile('./files/2.txt', 'utf8')) .then(() => thenFs.readFile('./files/3.txt', 'utf8')) .then(() => {...}) */注意链式调用必须在回调函数中要返回 Promise 实例对象(异步操作)
可以精确控制异步操作的顺序
通过 .catch 方法捕获错误 ⇧
如果 Promise 的链式操作中发生错误,可以使用
Promise.prototype.catch方法进行捕获和处理
|
|
如果不希望前面的错误导致后续的
.then()无法执行,则可以将.catch的调用提前到第一个异步操作后
|
|
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数new Promise的时候- 如果在
.then中传了第二个参数,并捕获了reject的报错,那么.catch捕获的只有resolve抛出的异常 - 如果
.then没有传第二个参数,说明.then中仅处理resolve,那么.catch将捕获reject信息和resolve的异常
- 如果在
总结在 JS Promises 中的错误处理 ⇧
有一个
getUserData(userId)函数,返回关于用户的信息,或者如果 userId 参数有问题的话会抛出一个错误以前的处理方式是添加常规的 try/catch 并且在 catch 块中处理错误
|
|
- 但是使用常规的
try/catch不能在 Promise 的异步代码中捕获出现的错误 - 在同步代码中不会有问题,所以执行将会被继续
- 但在异步代码中出现错误时,将收到一个
UnhandledPromiseRejection,并且程序将终止
增加一个 finally 的块可以更好地理解
|
|
- 在 try 块中调用了一个 fetchUserData 函数,它将在 Pending 状态返回一个 Promise
- 这个 catch 块会被忽视,因为在 try 块中没有错误。异步语句还没有运行
- finally 行显示在屏幕上
- 在异步代码中出现了一个错误,并且在控制台上的错误信息—— UnhandledPromiseRejectionWarning
为了避免 Promises 中出现未处理的 Rejections,应该总是在 catch 中处理
|
|
区分以下两种在 Promise 中提供的错误处理函数回调的方式
|
|
- 当原先的
promise的状态变为rejected时,f1f2的errorHandler都会执行 - 当原先的
promise的状态变为resolve时,执行成功的回调函数successHandler 当
successHandler中抛出错误,将只会在f2的.catch(errorHandler)中得到错误信息1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19new Promise((resolve, reject) => { setTimeout(() => { resolve('hello') }, 1000) }) .then(res => { console.log(res) // 打印hello return res + ' world' }) .then(res => { console.log(res) // return Promise.reject('error message') // 发生异常 throw 'error message' // 抛出异常 }) .then(res => { console.log(res) // 打印hello world }).catch(error => { console.log(error) // 接收throw抛出的异常信息 })
小结
.then的第二个参数,即接收错误处理的回调rejectCb,只能处理 **它之前的promise的reject**.catch可以处理之前Promise链上所有的错误,包括 在.then()中的resolve成功回调函数中的错误- 可以通过 throw 抛出异常
Promise 的静态方法 ⇧
Promise 静态方法,即将封装的方法存储在构造函数Promise上,由构造函数自身调用
Promise.all()、Promise.race()、Promise.allSettled()、Promise.any()方法相同点为用于将多个 Promise 实例,包装成一个新的 Promise 实例
处理多个异步操作,将多个异步操作写在数组中
promise-arr.js
|
|
静态方法方法的使用 ⇧
Promise.resolve(value)返回一个状态由给定 value 决定的Promise对象Promise.reject(reason)返回一个状态为 reject 的Promise对象,并将 reason 传递给对应的处理方法Promise.all()Promise.race()Promise.allSettled()Promise.any()Promise.prototype.finally()
Promise.resolve()
Promise.resolve(x)可以看作是new Promise(resolve => resolve(x))的简写,可以用于快速封装字面量对象或其他对象,将其封装成 Promise 实例
Promise.all() ⇧
业务需要请求2个地方(A和B)的数据,只有A和B的数据都拿到才能走下一步
网络请求A和网络请求B返回先后顺序未知,所以需要定义一个函数只有2个请求都返回数据才回调成功
Promise.all() 基本语法 ⇧
|
|
- 返回一个 新的 promise 对象
Promise.all()方法可接受一个数组作为参数,一般地 p1、p2、p3 都是 Promise 实例对象- p1、p2、p3 互相独立,参数之间不能相互依赖
- 如果不是,会自动先调用下
Promise.resolve()方法,将参数转为 Promise 实例,再进一步处理
Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且一般返回的每个成员都是 Promise 实例- 如果传递的 Iterable 为空,则返回一个已经解决的 Promise
Promise.all([]).then(res=>{console.log(res) // []})
- 如果传递的 Iterable 不包含 Promise
Promise.all([1,2,3]).then(res=>{console.log(res) // [1,2,3]})
- 如果传递的 Iterable 为空,则返回一个已经解决的 Promise
- p 的状态由
p1、p2、p3决定,分成两种情况- 1)只有
p1、p2、p3的状态都变成fulfilled,p 的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给 p 的回调函数。 - 2)只要
p1、p2、p3之中只要有一个被rejected,p 的状态就变成rejected,此时 第一个被reject的实例 的返回值,会传递给 p 的回调函数
- 1)只有
相较于
.then()方法的链式操作,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// 当给定可迭代对象中的所有 promise 已解决 const p1 = new Promise((resolve, reject) => { resolve(1) }) const p2 = new Promise((resolve, reject) => { resolve(2) }) Promise.all([p1, p2, 3]) .then(res => { console.log(res) // [1, 2, 3] }) // 当给定可迭代对象中的任何 promise 被拒绝时 const q1 = new Promise((resolve, reject) => { resolve(1) }) const q2 = new Promise((resolve, reject) => { reject(2) }) Promise.all([q1, q2, 3]) .then(res => { console.log(res) }) .catch(err => { console.log(err) // 2 })
此方法对于汇总多个 promise 的结果很有用, 在ES6中可以将多个
Promise.all异步请求并行操作:
- 当所有结果成功返回时按照请求顺序返回成功
- 当其中有一个失败方法时,则进入失败方法
Promise.all() 运行机制 ⇧
Promise.all()会发起并行的 Promise 异步操作,用于将多个 Promise 实例,包装成一个新的 Promise 实例等所有的异步操作全部结束后才会执行下一步的 .then 操作
即等待机制
|
|
Promise.all()参数数组中 Promise 实例的顺序,就是最终结果的顺序Promise.all()本身也是异步操作- 多个
Promise.all()运行看哪个先被执行,就先打印哪个
Promise.all() 的 Polyfill ⇧
|
|
为什么使用 Promise.all() ⇧
假设有一个异步方法
getUserData(),返回包含用户名、id、好友id列表的 Promise 对象1 2 3 4 5{ id: 125, name: 'Jack Jones', friends: [1, 23, 87, 120] }当此 Promise 实例对象的状态变为
fulfilled后才能接收到数据需要进一步按照好友id展示好友的所有数据
使用一个 Promise 实例对象来获得好友id列表
1getUserData(userId).then(console.log);使用
Array.prototype.map()分别打印出每个好友的数据1 2 3 4 5 6 7getUserData(userId) .then(userData => { return userData.friends.map(getUserData); // [1, 23, 87, 120].map(getUserData) }) .then(console.log) .catch(e => console.log(e.message));但只能得到
[Promise {<pending>}, Promise {<pending>}, Promise {<pending>}]列表中每项都是一个
pending状态的Promise,而不是数据
使用
Promise.all(Array<Promise>)接收promise对象数组,并返回一个单独的promise对象
- 当
Promise.all中所有的promise对象都变为fulfilled时,Promise.all返回的promise对象的状态才变为fulfilled Promise.all中任意一个promise对象变为rejected时,Promise.all返回的promise对象的状态就会变为rejected1 2 3 4 5 6 7getUserData(userId) .then(userData => { return Promise.all(userData.friends.map(getUserData)); // Promise.all([1, 23, 87, 120].map(getUserData)) }) .then(console.log) .catch(e => console.log(e.message));需要在最后同时得到多个promise对象返回的数据时使用
可以合并允许并行执行的异步操作,避免过渡使用
async/await造成的性能与代码执行效率的下降
Promise.al1一些应用场景
- 应用场景1:多个请求结果合并在一起
- 应用场景2:合并请求结果并处理错误
- 应用场景3:验证多个请求结果是否都是满足条件
Promise.al1 应用场景1:多个请求结果合并在一起
具体描述:一个页面,有多个请求,需求所有的请求都返回数据后,再一起处理渲染
- 每个请求的 loading 状态如果单独设置,多个的话可能导致多个 loading 重合
- 页面显示的内容,根据请求返回数据的快慢有所差异,具体表现在渲染的过程
- 为提升用户体验,可以采用所有请求返回数据后,再一起渲染
- 关闭请求的单独 loading 设置,通过 Promise.all 汇总请求结果
从开始到结束,只设置一个 loading 即可
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// 1. 获取轮播数据列表 function getBannerList() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('轮播数据') }, 300) }) } // 2. 获取店铺列表 function getStoreList() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('店铺数据') }, 500) }) } // 3. 获取分类列表 function getCategoryList() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('分类数据') }, 700) }) } function initLoad() { // loading.show() // 加载loading Promise.all([ getBannerList(), getStoreList(), getCategoryList() ]) .then(res => { console.log(res) // loading.hide() // 关闭loading }) .catch(err => { console.log(err) // loading.hide() // 关闭loading }) } // 数据初始化 initLoad()
Promise.all应用场景2:合并请求结果并处理错误
描述:需求单独处理一个请求的数据渲染和错误处理逻辑
有多个请求,就需要在多个地方写,逻辑分散,维护麻烦
- 能否把多个请求合并在一起,哪怕有的请求失败了,也捕获错误信息返回
只需要在一个地方处理这些数据和错误的逻辑即可
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// 1.获取轮播图数据列表 function getBannerList() { return new Promise((resolve, reject) => { setTimeout(function () { // resolve('轮播图数据') reject('获取轮播图数据失败啦') }, 300) }) } // 2.获取店铺列表 function getStoreList() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('店铺数据') }, 500) }) } // 3.获取分类列表 function getCategoryList() { return new Promise((resolve, reject) => { setTimeout(function () { resolve('分类数据') }, 700) }) } function initLoad() { // loading.show() Promise.all([ getBannerList().catch(err => err), getStoreList().catch(err => err), getCategoryList().catch(err => err) ]) .then(res => { console.log(res) // ["获取轮播图数据失败啦", "店铺数据", "分类数据"] // 判断返回数组结果 res[0] === '轮播图数据' ? render() : catchError() // 获取 轮播图数据 失败的逻辑 res[1] === '店铺数据' ? render() : catchError() // 获取 店铺列表数据 失败的逻辑 res[2] === '分类数据' ? render() : catchError() // 获取 分类列表数据 失败的逻辑 // loading.hide() }) .finally( loading.hide() ) } initLoad()有时候页面挂掉了,可能因为接口异常导致,或许只是一个无关紧要的接口挂掉了
找到一个接口挂掉了导致整个页面无数据的原因就需要用到
Promise.all如果参数中 promise 有一个失败(rejected)
- 此实例回调失败(reject)
- 就不再执行then方法回调
以上用例 正好可以解决此种问题
Promise.all应用场景3:验证 多个请求结果是否都是满足条件
描述:在一个微信小程序项目中,做一个表单输入内容安全验证
调用的是云函数写的方法,表单有多7个字段需要验证
统一调用一个 内容安全校验接口
全部验证通过才可以进行正常的提交
|
|
Promise.race() ⇧
Promise.race()基本语法 ⇧
|
|
- 只要
p1、p2、p3之中有一个实例率先改变状态,p 的状态就跟着改变 - 率先改变的 Promise 实例的返回值,就传递给 p 的回调函数
- 语法:
Promise.race(iterable) - 参数: iterable 可迭代的对象,例如 Array。可迭代的
- 返回值:
Promise.race(iterable)方法返回一个 promise- 一旦迭代器中的某个 promise 解决或拒绝,返回的 promise就会解决或拒绝
- 即其中任何一个 promise 最先 返回的结果(成功或失败)就是
Promise.race(iterable)的结果
Promise.race(iterable)函数返回一个 Promise- 它将与第一个传递的 promise 相同的完成方式被完成
- 它可以是完成( resolves),也可以是失败(rejects)
- 这要取决于第一个完成的方式是两个中的哪个
- 如果传的迭代是 空的,则返回的 promise 将永远等待
- 如果迭代包含 一个或多个非承诺值和/或已解决/拒绝的承诺
- 则
Promise.race()将解析为迭代中找到的 第一个值
- 则
Promise.race()示例 ⇧
Promise.race()也会发起并行的 Promise 异步操作只要 任意一个异步操作结束后 就立即执行下一步的
.then()操作即赛跑/竞态机制
|
|
Promise.race(iterable)方法返回一个 promise- 一旦迭代器中的某个promise解决或拒绝,返回的 promise 就会解决或拒绝
- 通过
Promise.race,能够达到当传入的 Promise 数组中的任意一个 Promise 达到了解决或拒绝时,就无视 Promise 数组中的其他 Promise 的结果的目的
Promise.race 的 Polyfill ⇧
|
|
为什么使用 Promise.race() ⇧
Promise.race(Array<Promise>)等待最先成功的promise对象,只要有一个先成功就忽略其他所有promise对象- 接收promise对象数组,并返回一个单独的promise对象
实际使用时无法事先预测其中哪个promise对象状态会第一个变为
fulfilled并返回值1 2 3 4 5 6 7 8 9 10 11const fastPromise = new Promise((resolve, reject) => { setTimeout(() => resolve(`fast`), 100); }); const slowPromise = new Promise((resolve, reject) => { setTimeout(() => resolve(`slow`), 200); }); const arr = [fastPromise, slowPromise]; Promise.race(arr).then(console.log); // fast
Promise.race()应用场景
- 应用场景1:图片请求超时
- 应用场景2:请求超时提示
Promise.race()应用场景1:图片请求超时
|
|
Promise.race()应用场景2:请求超时提示
描述:有些时候,前一秒刷着新闻,下一秒进入电梯后,手机页面上就会提示 “网络不佳”
|
|
Promise.allSettled() ⇧
Promise.allSettled()基本语法 ⇧
|
|
- 无论哪一个Promise实例是成功还是失败,都会将结果依次按请求顺序放到数组中
Promise.any() ⇧
Promise.prototype.finally()
Promise.prototype.finally()方法用于给期约添加 onFinally 处理程序- 在期约转换为 解决 或 拒绝 状态时都会执行
- 这个方法可以避免 onResolved 和 onRejected 处理程序中出 现冗余代码
- 将必定会执行的逻辑统一集中
但 onFinally 处理程序没有办法知道期约的状态是 解决 还是 拒绝,所以这个方法主要用 于添加清理代码
1
基于Promise封装异步读文件的方法 ⇧
形式上的异步操作 ⇧
方法的要求
- 命名为
getFile - 需要接受一个形参
filePath,表示要读取文件的路径 - 返回值为 Promise 实例对象
方法的基本定义
|
|
return new Promise()只是创建了一个形式上的异步操作- 实例化 new Promise(Cb) 实例中的回调函数Cb为同步任务
创建具体的异步操作 ⇧
创建具体的异步操作,需要在
new Promise()构造函数期间,传递一个function函数作为参数将具体的异步操作定义到 这个 function 函数内部
promise-getFile.js
|
|
获取 .then 的两个实参 ⇧
通过 .then() 预先指定的成功和失败的回调函数,可以在 function 的形参中进行接收
|
|
- 在
.then(用户指定成功的回调函数, 用户指定失败的回调函数)中传入resolve, reject作为new Promise(function (resolve, reject) {}形参列表的两个实参 - 在
new Promise(function (resolve, reject) {}中- 通过
resolve接收 .then 中用户指定成功的回调函数resolveHandler - 通过
reject接收 .then 中用户指定失败的回调函数rejectHandler
- 通过
- 当一个Promise被创建时,回调被立即执行
调用 resolve 和 reject 回调函数 ⇧
Promise 异步操作的结果,可以调用 resolve 或 reject 回调函数进行处理
|
|
- 如果读取文件失败,则调用 .then 中指定的“失败的”回调函数,并传入错误对象
err - 如果读取成功,则调用 .then 中指定的“成功的”回调函数,并传入
dataStr fs.readFile(filePath, 'utf8', (err, dataStr) => {})
promise-getFile.js
|
|
promise-then-getFile.js
|
|
- 运行
node promise/promise-getFile.js
总结常用返回 promise 对象的方法 ⇧
返回promise对象的方法
axiosfs.readFile(Node.js)
不返回 promise 对象的异步方法
setTimeout/setIntervaladdEventListener
试题 ⇧
Promise 是什么
- 用于表示一个异步操作的最终完成或失败及其结果值
Promise.all(iterable)怎么运作的?
Promise.all()方法传入一个 Promise 的 iterable 类型(Array、Map、Set),返回一个Promise实例- 这个 Promise 的 resolve 回调是在输入的所有 Promise 的 resolve 回调都结束
只要任何一个输入的 Promise 的 reject 回调执行,立即抛出错误
1 2 3 4 5 6 7 8 9 10const promise1 = Promise.resolve(3); const promise2 = 42; const promise3 = new Promise((resolve, reject) => { setTimeout(resolve, 100, 'foo'); }); Promise.all([promise1, promise2, promise3]).then((values) => { console.log(values); }); // output: Array [3, 42, "foo"]
Promise.reject(1).then(console.log('then1')).catch(console.log('catch1')).then(console.log('then2')).catch(console.log('catch2'))运行到哪一步和运行结果
catch方法返回一个 Promise 实例对象,并且处理拒绝的情况,它的行为和 then 是相同的1 2 3 4 5 6 7 8 9 10 11 12 13 14let p = Promise.reject(1).then(console.log('then1')).catch(console.log('catch1')).then(console.log('then2')).catch(console.log('catch2')) console.log(p) setTimeout(() => { console.log(p); }) //输出 then1 catch1 then2 catch2 Promise { <pending> } (node:54428) UnhandledPromiseRejectionWarning: 1 Promise { <rejected> 1 }
reject(1)方法,返回一个状态为失败的Promise对象- 返回失败状态的 Promise 调用
.then(console.log('then1'))方法,但是其中没有这种状态的回调函数,所以创建并返回一个和原始 Promise 失败状态相同的 Promise 实例对象 catch(console.log('catch1')),调用catch方法,但是其中没有这种状态的回调函数,所以返回一个失败状态的Promise,输出catch1- catch 和 then本质上区别不大,最后都会返回一个新的 Promise对象
- Promise 状态变成 fulfilled 或者 rejected 后不可以改变状态
参考文章
- JavaScript, Promise creation and Promise chains
- 使用 Promise MDN
- Promise MDN
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / 简介:回调
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / Promise
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / Promise 链
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / 使用 promise 进行错误处理
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / Promise API
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / Promisification
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / 微任务(Microtask)
- 现代 JavaScript 教程 / JavaScript 编程语言 / Promise,async/await / Async/await
- 现代 JavaScript 教程 / JavaScript 编程语言 / Generator,高级 iteration / 异步迭代和 generator
- Wangdoc 异步操作概述
- 阮一峰 ES6 Promise 对象
- axios 中文文档
- axios/axios
- https://axios-http.com/
- Promise源码
- PromiseA+规范
- ecma262: Promise Abstract Operations
相关文章
- JS异步编程模型与Promise初探
- 使用 Promise 时的5个常见错误
- axios, ajax和fetch的比较
- 如何中断Promise?
- Promise深度解析10个常用模块
- 前端 Promise 常见的应用场景
- async/await 之于 Promise,正如 do 之于 monad(译文)