为什么需要使用 Promise ?
传统回调函数的代码层层嵌套,形成回调地狱,难以阅读和维护,为了解决回调地狱的问题,更加优雅地书写复杂的异步任务,诞生了 Promise
什么是 Promise ?
Promise 是一种异步编程的解决方案,本身是一个构造函数,可通过 new 创建未来会执行的对象实例(一个状态为 pending 的 Promise 实例 )
console.log(Promise); // [Function: Promise]
自带 resolve,reject,all 等方法,其原型上还有 then、catch 等方法。
Promise 的三种状态及其变化
- pending 进行中,不会触发 then 和 catch 回调函数
- resolved / fulfilled 已成功,会触发后续的 then 回调函数
- rejected 已失败,会触发后续的 catch 回调函数
Promise 的状态变化如上图所示,不可逆
-
Promise 最初的状态是 pending
-
pending 状态的 Promise 执行 resolve() 后,状态变为 resolved
Promise.resolve(); // Promise 的状态从 pending 变为 resolved
-
resolved 状态的 Promise 会触发后续的 then 函数,
-
若 then 函数内没有报错,则返回一个 resolved 状态的 Promise
Promise.resolve().then(() => {}); // 最终 Promise 的状态为 resolved
-
若 then 函数内报错,则返回一个 rejected 状态的 Promise
Promise.resolve().then(() => { throw new Error("then函数出现报错"); }); // 最终 Promise 的状态为 rejected
-
-
pending 状态的 Promise 执行 reject() 后,状态变为 rejected
Promise.reject(); // Promise 的状态从 pending 变为 rejected
-
rejected 状态的 Promise 会触发后续的 catch 函数,
-
若 catch 函数内没有报错,则返回一个 resolved 状态的 Promise
Promise.reject().catch(() => {}); // 最终 Promise 的状态为 resolved
-
若 catch 函数内报错,则返回一个 rejected 状态的 Promise
Promise.reject().catch(() => { throw new Error("catch函数出现报错"); }); // 最终 Promise 的状态为 rejected
-
创建 Promise
新创建的 Promise 实例的状态为 pending
// 此时,p1 的状态为 pending
const p1 = new Promise((resolve, reject)=>{
})
执行 resolve() ,Promise 实例的状态变为 fulfilled
const p1 = new Promise((resolve, reject) => {
resolve()
})
执行 reject() ,Promise 实例的状态变为 rejected
const p1 = new Promise((resolve, reject) => {
reject()
})
Promise.resolve()
- 不传参数时,直接返回一个resolved状态的 Promise 对象。
Promise.resolve().then(function () {
console.log('two'); // two
});
- 参数为 Promise 实例时,原封不动地返回这个实例。
- 参数为 thenable对象(有then方法的对象)时
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
Promise.resolve 会将这个对象转为 Promise 对象,然后就立即执行 then方法。
let thenable = {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {
console.log(value); // 42
});
- 参数若没有 then 方法,返回一个新的状态为 resolved 的 Promise 对象。
const p = Promise.resolve('Hello');
p.then(function (s){
console.log(s) // Hello
});
Promise.resolve方法的参数,会同时传给回调函数
Promise 的 then 方法
- 第一个参数是 Promise 成功解析时要调用的函数,该函数的参数为 Promise 的结果(可选)
- 第二个参数是 Promise 被拒绝时要调用的函数(可选)
- 返回一个新的 Promise 实例,其结果为第一个参数的返回值,若第一个参数的返回值为一个 Promise 实例,则 then 方法的返回值会是该 Promise 实例的深拷贝
// p1为结果为 100 的 fulfilled 状态的 Promise 实例
let p1 = new Promise((resolve, reject) => {
resolve(100);
});
// p2为结果为 200 的 fulfilled 状态的 Promise 实例
let p2 = p1.then((res) => {
console.log(res); // 打印 p1 的结果 100
return 200;
});
若 then() 不传参数,则返回一个 pending 状态的 Promise
如果有多个 fulfilled promise 实例,同时执行 then 链式调用,then 会交替执行,这是编译器的优化,防止一个 promsie 占据太久时间
then 中返回promise 实例,相当于多出一个 promise 实例,也会遵守“交替执行”
(但和直接声明一个 promise 实例,结果有些差异)
then 中返回的 promise 实例,需要经历两次微任务,才能执行其 then 函数
- 将 promise 的状态从 pending 变为 fulfilled
- 将 then 函数放入微任务
待第3次微任务,才会执行返回的 promise 实例的 then 函数
// 面试题
Promise.resolve()
.then(() => {
console.log(0);
return Promise.resolve(4);
})
.then((res) => {
console.log(res);
});
Promise.resolve()
.then(() => {
console.log(1);
})
.then(() => {
console.log(2);
})
.then(() => {
console.log(3);
})
.then(() => {
console.log(5);
})
.then(() => {
console.log(6);
});
执行结果
0
1
2
3
4
5
6
Promise 的串行执行
用 async await 实现最便捷
async function promise_serial() {
let result = [];
result.push((await promise_list[2]).data);
result.push((await promise_list[1]).data);
result.push((await promise_list[0]).data);
console.log(result);
}
promise_serial();
另一种思路是:将 promise 包装为函数返回值,通过 then 链式调用执行
import axios from "axios";
let id_list = ["1", "2", "3"];
let promise_list = [];
for (let id of id_list) {
promise_list.push(
axios.get(`http://jsonplaceholder.typicode.com/users/${id}`)
);
}
let p_func_dic = {};
promise_list.forEach((p_item, index) => {
p_func_dic[index] = function () {
return p_item;
};
});
let result = [];
p_func_dic[2]()
.then((res) => {
result.push(res.data);
return p_func_dic[1]();
})
.then((res) => {
result.push(res.data);
return p_func_dic[0]();
})
.then((res) => {
result.push(res.data);
console.log(result);
});
当然,在 then 中执行下一个 Promise 的层层嵌套方式也是 Promise 串行,但因行成 回调地狱
,不方便维护,不推荐使用。
Promise 的并行执行 —— Promise.all()
Promise .all() 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
- p1、p2、p3 都是 Promise 实例,得到的 p 也是Promise 实例
- Promise .all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
- p1、p2、p3 的状态都变成 fulfilled, p 的状态才会变成 fulfilled
- p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected
- p 的 then 函数中得到的是 p1、p2、p3 的返回值组成的一个数组
promise.all 实战范例
import axios from 'axios'
let infoList = []
let id_list = ['1', '2', '3']
let promise_list = []
for (let id of id_list) {
promise_list.push(axios.get(`http://jsonplaceholder.typicode.com/users/${id}`))
}
Promise.all(promise_list).then((res) => {
infoList = res.map((item) => item.data)
console.log(infoList) // 得到预期结果
})
手写 promise.all
function pAll (_promises) {
return new Promise((resolve, reject) => {
// Iterable => Array
const promises = Array.from(_promises)
// 结果用一个数组维护
const r = []
const len = promises.length
let count = 0
for (let i = 0; i < len; i++) {
// Promise.resolve 确保把所有数据都转化为 Promise
Promise.resolve(promises[i]).then(o => {
// 因为 promise 是异步的,保持数组一一对应
r[i] = o;
// 如果数组中所有 promise 都完成,则返回结果数组
if (++count === len) {
resolve(r)
}
// 当发生异常时,直接 reject
}).catch(e => reject(e))
}
})
}
Promise 自测题
此时仅创建了 Promise 对象,没有执行 resolve() 或 reject(),所以状态是 pending
- 因 setTimeout 是异步任务,内部代码在打印完 p2 后才执行,所以在打印 p2 时,Promise 还没执行 resolve() ,状态是 pending。
- 打印完 p2 后,setTimeout 内的 resolve() 执行,Promise 的状态变为 resolved