一、Promise是什么?
1. 理解概念
-
抽象来说,Promise是JS中进行异步编程的新的解决方案(旧方案是通过回调函数)
-
具体表达:
① Promise是一个构造函数
②Promise对象用来封装一个异步操作,并可以获取其成功/失败的结果
-
引用阮一峰老师的话: 所谓
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
2. Promise状态
- 对象的状态不受外界影响。 有三种状态:
pending(进行中)、fulfilled(已成功)和rejected(已失败)。 - 一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。
3. Promise对象的值
实例对象中PromiseResult保存Promise成功或者失败的结果值。通过调用resolve/reject来改变结果值和状态。
4. Promise基本流程
二、 为什么要用Promise?
1. 指定回调函数的方式更加灵活
- 旧的方式:必须在启动异步任务之前指定回调
- Promise:启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至可以指定多个回调)
2. 支持链式调用,解决回调地狱问题
-
什么是回调地狱?
有时候,我们需要拿到一个异步任务的结果,才能继续下一个异步任务,也就是回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。
-
回调地狱的缺点?
不便于阅读,不便于异常处理。
-
通过Promise的链式调用可以解决这个问题
-
终极解决方案:
async/await
三、Promise的使用
1. API
(1) Promise构造函数:Promise(excutor) {}
-
excutor函数:执行器 (resolve, reject) => {}
-
resolve函数:Promise内部定义成功时我们调用的函数,改变状态为成功,并返回成功结果 value => {}
-
reject函数:Promise内部定义失败时我们调用的函数,改变状态为失败,并返回失败结果 reason => {}
说明:excutor 会在 Promise 内部立即同步调用,异步操作在执行器中执行
// 先输出111,再输出222,,说明Promise构造函数的参数(执行器函数)是在Promise内部立即同步调用的
let p1 = new Promise((resolve, reject) => {
// 同步调用
console.log('111');
})
console.log('222');
(2) Promise.prototype.then 方法: (onResolved, onRejected) => {}
- onResolved 函数: 成功的回调函数 (value) => {}
- onRejected 函数: 失败的回调函数 (reason) => {} 说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象
(3) Promise.prototype.catch 方法: (onRejected) => {}
- onRejected 函数: 失败的回调函数 (reason) => {} 说明: then()的语法糖, 相当于: then(undefined, onRejected)
(4) Promise.resolve 方法: (value) => {} 用于快速得到一个Promise对象
- value: 成功的数据或 promise 对象 说明: 返回一个成功/失败的 promise 对象
let p1 = Promise.resolve(521);
console.log(p1); // 返回一个promise对象,状态为成功,且值为521
// 如果传入的参数为 非promise类型的对象,则返回的结果为成功的promise对象
// 如果传入的参数为 Promise 对象,则参数的结果决定了 resolve 的结果
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK');
reject('error')
}))
console.log(p2); // 如果传入的Promise为成功,则p2也为成功,值为内部成功的值,如果传入的Promise为失败,则p2为失败,值为内部失败的值
p2.catch(reason => {
console.log(reason); // 'error'
});
(5) Promise.reject 方法: (reason) => {}
- reason: 失败的原因 说明: 返回一个失败的 promise 对象
let p = Promise.reject(521);
let p2 = Promise.reject('iloveyou');
let p3 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p); // 状态为失败,且失败的值为521
console.log(p2); // 状态为失败,且失败的值为'iloveyou'
console.log(p3); // 即使传入参数为成功的Promise,状态仍然为失败,失败的值为传入的那个Promise
(6) Promise.all 方法: (promises) => {}
- promises: 包含 n 个 promise 的数组 说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就 直接失败
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Sucess');
let p3 = Promise.resolve('Oh Yeah');
let p4 = Promise.reject(123);
let p5 = Promise.reject(321)
//
const result = Promise.all([p1, p2 ,p3]); // 传入的Promise数组都为成功
console.log(result); // 则返回的Promise状态也为成功,成功的值为所有Promise成功结果值组成的数组['OK', 'Sucess', 'Oh Yeah']
const res = Promise.all([p1,p2,p3,p4,p5]); // 传入的p4,p5为失败的Promise
console.log(res); // 只要传入有一个失败,则返回的Promise状态为失败,失败的值为第一个失败的Promise的值123
(7) Promise.race 方法: (promises) => {}
- promises: 包含 n 个 promise 的数组 说明: race有竞赛的意思,这个函数返回一个新的 promise, 传入promise数组中第一个完成的 promise 的结果状态就是最终的结果状态
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Sucess');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.race([p1,p2,p3]);
console.log(result); //在p1没有包裹定时器之前很明显p1先改变状态,所以此时返回promise为成功,且值为'OK'
// 当p1包裹定时器之后,p2先改变状态,所以返回的promise为p2状态成功,值为'Sucess'
2. Promise的几个关键问题
(1) 如何改变 promise 的状态?
- resolve(value): 如果当前是 pending 就会变为 resolved
- reject(reason): 如果当前是 pending 就会变为 rejected
- 抛出异常: 如果当前是 pending 就会变为 rejected
// 改变promise状态的三种方式
let p = new Promise((resolve, reject) => {
// 1. resolve函数
// resolve('ok'); // pending => fulfilled(resolved)
// 2. reject函数
// reject('error'); // pending => rejected
// 3. 抛出错误
throw '出问题了'; // pending => rejected 值为 '出问题了'
})
console.log(p);
(2) 一个 promise 指定多个成功/失败回调函数, 都会调用吗?
- 当 promise 改变为对应状态时都会调用
let p = new Promise((resolve, reject) => {
resolve('OK');
});
// 指定回调 - 1
p.then(value=>{
console.log(value);
});
// 指定回调 - 2
p.then(value=>{
alert(value);
})
// p中指定resolve改变状态为成功,则成功回调1、2都会执行,即弹窗,也console.log,
// 如果注释掉resolve('OK'),Promise状态为pending,则两个成功回调均不执行
(3) 改变 promise 状态和指定回调函数谁先谁后?
- 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
- 如何先改状态再指定回调? ① 在执行器中直接调用 resolve()/reject() ② 延迟更长时间才调用 then()
- 什么时候才能得到数据? ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据 ② 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
(4) promise.then()返回的新 promise 的结果状态由什么决定?
- 简单表达: 由 then()指定的回调函数执行的结果决定
- 详细表达: ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常 ② 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值 ③ 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果
let p = new Promise((resolve, reject) => {
resolve('OK');
});
// 执行then方法
let result = p.then(value=>{
// 1. 抛出错误 , then返回的promise也是错误,值为'出问题了'
// throw '出问题了';
// 2. 返回结果是非Promise对象 then返回的promise为正确,值为return的521
// return 521;
// 3. 返回结果是Promise对象 // then返回的值根据下面的Promise决定,正确则正确,错误则也错误,值相同。
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
})
})
console.log(result);
(5) promise 如何串连多个操作任务?
- 由于promise 的 then()返回一个新的 promise, 因此 then()可以链式调用
- 通过 then 的链式调用串连多个同步/异步任务
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
p.then(value => {
return new Promise((resolve, reject) => {
resolve('success');
});
}).then(value => {
console.log(value); // 'success'
}).then(value => {
console.log(value); // undefined , 由于上一个then回调函数没指定返回值,默认返回undefined(非Promise对象),上一个then返回成功的promise,值为undefined
})
(6) promise 异常传透是什么?
- 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调,
- 前面任何操作出了异常, 都会传到最后失败的回调中处理
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 1000);
})
p.then(value => {
// console.log(111);
throw '失败啦!'
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason); // 如果第一个Promise错误,返回'Err',如果第二个Promise 抛出异常,返回'失败啦!'
})
// 异常穿透:前面的Promise如果状态错误,,可以穿透到最后一个promise对象再来处理
(7) 如何中断 promise 链?
- 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
- 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
p.then(value => {
console.log(111);
// 有且只有一种方式中断Promise链, 就是返回一个pendding状态的Promise对象,
// 因为这样then返回的也是pending状态的promise,这样后面的then就都不会执行了,因为promise状态没有改变
// then方法中指定的回调只有在promise状态改变之后才会执行
return new Promise(() => {});
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
})