1、Promise 是一个构造函数,我们可以通过该构造函数来生成Promise的实例。
2、Promise 即承诺,后续必要兑现,一旦兑现则不可更改!其状态有三:pending(等待)、resolved(成功)、rejected(失败)。
3、Promise 是对回调函数的一种封装,是对异步编程的一种改进(传统解决异步编程的方式是通过回调函数,而回调嵌套过多会导致回调地狱(callbackhell)),我们可以通过Promise将自己的程序以同步的方式表达出来,从而可以解决代码臃肿及可读性差的问题。
4、Promise 的实例可以看做是一个状态展示器,我们可以将拥有状态及改变状态的业务通过Promise来实现,然后再结合async function进一步提升程序的可读性及易维护性。
5、与前端相关的很多技术体系库均采用了Promise对象,比如Axios、antd等等,所以了解、熟练并合理的使用它是一名合格前端必备的技能。
6、在实际项目的开发中经常会遇到继某一个函数执行结果之后,再进行入后续操作的场景,通过Promise就是一种非常好的解决方案。比如我们可以通过Promise对ajax进行封装用于实现业务逻辑与数据的分离。
7、Promise虽然解决了我们项目开发中的很多问题,但我们也不能无脑的滥用。比如Promise.all,如果参数中promise有一个失败(rejected),则此实例回调必然失败(reject),就不会再执行then方法的回调了。在实际中可能只是一个不关键的数据加载失败,往往会导致其他所有的数据不会显示,使得项目的容错性大大降低。所以我个人在开发过程中只会在必须依赖这几个步骤全部加载成功后才能继续向下执行的场景中采用它,比如图片的预加载功能。
2 使用 promise 封装 ajax 异步请求
- 为什么要用Promise? 2.1 指定回调函数的方式更加灵活 旧的: 必须在启动异步任务前指定 promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函 数(甚至可以在异步任务结束后指定/多个) 2.2 支持链式调用, 可以解决回调地狱问题 2.2.1 什么是回调地狱 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调执行的条件
2.2.2 回调地狱的缺点? 不便于阅读 不便于异常处理
2.2.3 解决方案? promise 链式调用,
用来解决回调地狱问题,但是只是简单的改变格式,并没有彻底解决上面的问题真正要解决上述问题,一定要利用promise再加上await和async关键字实现异步传同步
2.2.4 终极解决方案? promise +async/await
- Promise中的常用 API 概述 此处列举几个最常用的API的概述,如果想看详细描述的可以继续往下看下方的 Promise方法的具体使用 描述
3.1 Promise 构造函数: Promise (excutor) {} (1) executor 函数: 执行器 (resolve, reject) => {}
(2) resolve 函数: 内部定义成功时我们调用的函数 value => {}
(3) reject 函数: 内部定义失败时我们调用的函数 reason => {}
说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行,换话说Promise支持同步也支持异步操作
3.2 Promise.prototype.then 方法: (onResolved, onRejected) => {} (1) onResolved 函数: 成功的回调函数 (value) => {}
(2) onRejected 函数: 失败的回调函数 (reason) => {}
说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象
3.3 Promise.prototype.catch 方法: (onRejected) => {} (1) onRejected 函数: 失败的回调函数 (reason) => {}
说明: then()的语法糖, 相当于: then(undefined, onRejected)
(2) 异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中
3.4 Promise.resolve 方法: (value) => {} (1) value: 成功的数据或 promise 对象
说明: 返回一个成功/失败的 promise 对象,直接改变promise状态
3.5 Promise.reject 方法: (reason) => {} (1) reason: 失败的原因
说明: 返回一个失败的 promise 对象,直接改变promise状态,代码示例同上
3.6 Promise.all 方法: (promises) => {} promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一 个失败了就直接失败
let p1 = new Promise((resolve, reject) => { resolve('成功'); }) let p2 = Promise.reject('错误错误错误'); let p3 = Promise.resolve('也是成功') const result = Promise.all([p1, p2, p3]); console.log(result); 1 2 3 4 5 3.7 Promise.race 方法: (promises) => {} (1) promises: 包含 n 个 promise 的数组
说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态,
如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1,结果是P2
let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('OK'); }, 1000); }) let p2 = Promise.resolve('Success'); let p3 = Promise.resolve('Oh Yeah'); //调用 const result = Promise.race([p1, p2, p3]); console.log(result); 1 2 3 4 5 6 7 8 9 10 4、Promise的几个关键问题(重点) 4.1 如何改变 promise 的状态? (1) resolve(value): 如果当前是 pending 就会变为 resolved
(2) reject(reason): 如果当前是 pending 就会变为 rejected
(3) 抛出异常: 如果当前是 pending 就会变为 rejected
4.2 一个 promise 指定多个成功/失败回调函数, 都会调用吗? 当 promise 改变为对应状态时都会调用,改变状态后,多个回调函数都会调用,并不会自动停止
let p = new Promise((resolve, reject) => { resolve('OK');}); ///指定回调 - 1 p.then(value => { console.log(value); }); //指定回调 - 2 p.then(value => { alert(value);});
4.3 改变 promise 状态和指定回调函数谁先谁后?
(1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
先指定回调再改变状态(异步):先指定回调–> 再改变状态 -->改变状态后才进入异步队列执行回调函数
先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调
(2) 如何先改状态再指定回调? -->注意:指定并不是执行
① 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作
② 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法
(3) 什么时候才能得到数据?
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
let p = new Promise((resolve, reject) => { //异步写法,这样写会先指定回调,再改变状态 setTimeout(() => {resolve('OK'); }, 1000); //这是同步写法,这样写会先改变状态,再指定回调 resolve('OK'); }); p.then(value => {console.log(value);}, reason => {}) 1 2 3 4 5 6 7 4.3 改变 promise 状态和指定回调函数谁先谁后? (1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
先指定回调再改变状态(异步):先指定回调–> 再改变状态 -->改变状态后才进入异步队列执行回调函数
先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调
(2) 如何先改状态再指定回调? -->注意:指定并不是执行
① 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作
② 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法
(3) 什么时候才能得到数据?
① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
二、Promise+ async + await 1)Promise==>异步
2)await==>异步转同步
await 可以理解为是 async wait 的简写。await 必须出现在 async 函数内部,不能单独使用。 await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。如果await的是 promise对象会造成异步函数停止执行并且等待 promise 的解决,如果等的是正常的表达式则立即执行 3)async==>同步转异步
方法体内部的某个表达式使用await修饰,那么这个方法体所属方法必须要用async修饰所以使用awit方法会自动升级为异步方法
- async函数 函数的返回值为 promise 对象 promise 对象的结果由 async 函数执行的返回值决定
- await表达式 await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
如果表达式是 promise 对象, await 返回的是 promise 成功的值
如果表达式是其它值, 直接将此值作为 await 的返回值
3.注意 await 必须写在 async 函数中, 但 async 函数中可以没有 await 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理 三、宏任务与微任务 JS中用来存储待执行回调函数的队列包含2个不同特定的列队 宏队列:用来保存待执行的宏任务(回调),比如:定时器回调/ajax回调/dom事件回调 微队列:用来保存待执行的微任务(回调),比如:Promise的回调/muntation回调 JS执行时会区别这2个队列: JS执行引擎首先必须执行所有的初始化同步任务代码 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行 同步任务 --> 微任务 --> 宏任务
- 代码与示例
setTimeout(() => {
console.log('timeout callback1()')//立即放入宏队列 Promise.resolve(3).then( value => { console.log('Promise onResolved3()', value)//当这个宏任务执行后 立马放入微队列,所以这个微任务执行完后下个宏任务才能执行
}
) }, 0)
setTimeout(() => { console.log('timeout callback2()') //立即放入宏队列, }, 0)
Promise.resolve(1).then( value => { console.log('Promise onResolved1()', value)//立即放入微队列 setTimeout(() => { console.log('timeout callback3()', value) //立即放入宏任务 }, 0) } )
Promise.resolve(2).then( value => { console.log('Promise onResolved2()', value)//立即放入微队列 } ) console.log('同步代码') //同步代码立即执行
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 onResolved1()', 'Promise onResolved2()', 'timeout callback1()', 'Promise onResolved3()', 'timeout callback2()', 'timeout callback3()' 1 2 3 4 5 6 7 1.2 尝试自己思考下
setTimeout(() => console.log('代码开始执行'),0) new Promise((resolve,reject) => { console.log('开始for循环'); for(let i=0;i<10000;i++){ i == 99 && resolve() } }).then(() => console.log('执行then函数')) console.log('代码执行结束'); 1 2 3 4 5 6 7 8 开始for循环 代码执行结束 执行then函数 代码开始执行 e onResolved1()', value)//立即放入微队列 setTimeout(() => { console.log('timeout callback3()', value) //立即放入宏任务 }, 0) } )
Promise.resolve(2).then( value => { console.log('Promise onResolved2()', value)//立即放入微队列 } ) console.log('同步代码') //同步代码立即执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 '同步代码', 'Promise onResolved1()', 'Promise onResolved2()', 'timeout callback1()', 'Promise onResolved3()', 'timeout callback2()', 'timeout callback3()' 1 2 3 4 5 6 7 1.2 尝试自己思考下 setTimeout(() => console.log('代码开始执行'),0) new Promise((resolve,reject) => { console.log('开始for循环'); for(let i=0;i<10000;i++){ i == 99 && resolve() } }).then(() => console.log('执行then函数')) console.log('代码执行结束');