Promise基础
Promise是一套专门处理异步场景的规范,它能避免回调地域的产生,使异步代码更加清晰、简洁、统一
Promise规范
Promise规范大致如下:
-
所有的异步场景,都可以看作是一个异步任务,并且异步任务应该表现为一个对象,称之为Promise对象
-
每个Promise对象,应该有三个状态、两个阶段
状态:挂起状态(pending)、完成状态(fullfilled)、失败状态(rejected)
阶段:未决阶段(unsettled)、已决阶段(settled)
当Promise处于未决阶段时,它的状态只能为pending,当Promise处于已决阶段时,它的状态可能是fulfilled,也可能是rejected
注意:
- Promise总是从未决阶段进入已决阶段,无法逆向变化
- Promise一旦进入已决阶段,状态也将固定下来,之后无法改变
-
状态从pending转变为fulfilled,这种变化称为resolve
状态从pending转变为rejected,这种变化称为reject
resolve时可以携带一个相关数据,reject时可以携带一个失败原因
-
可以对处于已决阶段的Promise对象进行下一步处理(称为后续处理)
针对fulfilled状态的Promise对象的后续处理称为onFulfilled,针对reject状态的Promise对象的后续处理称为onRejected
onFulfilled的处理过程中可以拿到resolve时携带的相关数据,onRejected的处理过程中可以拿到reject时携带的失败原因
Promise API
创建一个Promise对象:
const pro = new Promise(()=>{});
console.log(pro); // Promise {<pending>}
创建Promise对象时需要传入一个回调函数,该回调函数就描述了任务的具体过程
传递给Promise构造函数的回调是立即执行的(同步执行的)
new Promise(()=>{
console.log(1);
});
console.log(2);
/*
1
2
*/
回调函数中可以接收两个函数参数:resolve和reject,当调用resolve时,处于pending状态的Promise对象就会进入fulfilled状态,当调用reject时,处于pending状态的Promise对象就会进入rejected状态
在调用resolve或reject时可以传入参数,而参数就是变化发生时所携带的信息
const pro = new Promise((resolve, reject)=>{
if(...){
resolve("相关数据");
} else {
reject("失败原因");
}
});
console.log(pro);
// Promise {<fulfilled>: "相关数据"}
// Promise {<rejected>: "失败原因"}
每个Promise对象中都有一个then方法,then方法中可以传入两个函数参数,其中第一个函数参数就是Promise对象进入fulfilled状态时的后续处理onFulfilled,第二个参数就是Promise对象进入rejected状态时的后续处理onRejected
onFulfilled中拿到的相关数据就是调用resolve时传入的信息,onRejected中拿到的失败原因就是调用reject时传入的信息
const pro = new Promise((resolve, reject)=>{
if(...){
resolve("相关数据");
}else {
reject("失败原因");
}
});
pro.then(
(data)=>{ // onFulfilled
console.log(data); // "相关数据"
},
(reason)=>{ // onRejected
console.log(reason); // "失败原因"
}
);
注意:
-
尽管在给Promise对象配置then方法之前,Promise对象的状态就已经变为了fulfilled了,但then方法中的onFulfilled仍然能够执行
-
如果new Promise()中的回调抛出了错误,则该Promise对象会直接进入rejected状态,失败原因为错误对象
注意:该回调中抛出的错误只会影响回调中的后续代码,不会影响到外部
const pro = new Promise(()=>{ throw new Error("error"); console.log("abc"); // 不执行 }); // 正常执行 console.log(pro); // Promise {<rejected>: Error: error...}
Promise的链式调用
catch方法
catch方法中传入的回调就是onRejected
pro.catch(()=>{})
// 等效于
pro.then(null, ()=>{});
链式调用
then和catch方法都会返回一个新的Promise对象
const pro1 = new Promise(()=>{});
const pro2 = pro1.then();
console.log(pro2); // Promise {<pending>}
then和catch方法返回的Promise对象,它的状态转变不同于直接new Promise()创建的Promise对象
then和catch方法返回的Promise对象的状态转变规则如下:
-
若上一个Promise对象还处于未决阶段,则then或catch返回的新Promise对象的状态和上一个Promise对象一样,也为pending
-
若上一个Promise对象的进入了已决阶段,但then或catch中没有对应的进一步处理,则then或catch返回的新Promise对象的状态和数据最终会与上一个Promise对象的保持一致
const pro1 = new Promise((resolve)=>{ resolve("pro1完成后的数据"); // pro1的状态变为fulfilled }); const pro2 = pro1.then(); // then中没有onFulfilled console.log(pro2); // Promise {<pending>} setTimeout(()=>{ console.log(pro2); // Promise {<fulfilled>: "pro1完成后的数据"} console.log(pro1 === pro2); // false // 注意:只是状态和数据与上一个一致,并不是新Promise对象就是上一个Promise对象 }, 0); -
若上一个Promise对象的进入了已决阶段,且then或catch中有对应的后续处理,则根据后续处理的执行情况确定新Promise的状态和数据
① 后续处理执行无错,则新Promise的状态变为fulfilled,数据为后续处理的返回结果
② 后续处理执行出错,则新Promise的状态变为rejected,数据为抛出的错误对象
③ 后续处理中返回的是一个Promise对象,则新Promise对象的状态和数据与返回的Promise一致(但两者并不是同一个Promise)
注意:
-
then或catch中的onFulfilled和onRejected是被放入到微队列中,它们是异步执行的
-
如果一个Promise的状态受另一个Promise状态的影响,则由于另一个Promise的状态有可能是异步改变的,因此该Promise的状态一定是异步改变的
finally方法
finally中的回调既不是onFulfilled,也不是onRejected
只要上一个Promise已决,finally中的回调就会运行
finally中的回调在运行时,既拿不到上一个Promise完成后的相关数据,也拿不到上一个Promise失败后的相关原因
若finally中的回调执行无错,且回调的返回值不是Promise,则finally返回的Promise的状态和数据与上一个Promise保持一致
若finally中的回调执行无错,但回调的返回值是Promise:若回调返回的Promise状态为fulfilled,则finally返回的Promise状态和数据与上一个Promise保持一致;否则finally返回的Promise的状态和数据会与回调返回的Promise保持一致
若finally中的回调执行有错,则其返回的Promise的状态会变为rejected,失败原因为抛出的错误
const pro1 = new Promise((resolve, reject) => {
resolve(1);
});
const pro2 = pro1.finally(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
}, 0);
});
});
const pro3 = pro1.finally(() => {
throw 3;
});
const pro4 = pro1.finally(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(4);
}, 0);
});
});
setTimeout(() => {
console.log(pro2); // Promise {<fulfilled> 1}
console.log(pro3); // Promise {<rejected> 3}
console.log(pro4); // Promise {<rejected> 4}
}, 100);
Promise的静态方法
-
Promise.resolve(data)
若data不是一个Promise,则相当于:
new Promise((resolve)=>{ resolve(data); });若data是一个Promise,则直接返回data
const pro1 = Promise.reject(1); const pro2 = Promise.resolve(pro1); console.log(pro2 === pro1); // true若data像一个Promise(即data并不是ES6标准的Promise,但满足Promise规范),则返回的Promise的状态与数据与data保持一致
-
Promise.reject(reason)
直接返回一个rejected状态的Promise
它等同于:
new Promise((resolve, reject)=>{ reject(reason); }); -
Promise.all(Promise集合)
Promise集合本质上是一个可迭代对象
返回一个Promise,当传入的Promise集合全部fulfilled时该Promise将转变为fulfilled,若集合中出现一个rejected则该Promise将转变为rejected,若集合中没有rejected且存在pending则该Promise的状态为pending
若返回的Promise状态为fulfilled,则其数据为Promise集合中所有Promise的数据组成的数组
若返回的Promise状态为rejected,则其数据为Promise集合中第一个rejected的Promise的数据一致
若Promise集合中有不是Promise的元素,则内部会自动使用Promise.resolve()对其包裹
若传入的Promise集合是一个空集合,则all直接返回一个相关数据为空数组的成功Promise
-
Promise.any(Promise集合)
Promise集合本质上是一个可迭代对象
返回一个Promise,当传入的Promise集合出现一个fulfilled时该Promise将转变为fulfilled,若全部rejected则该Promise将转变为rejected,若集合中没有fulfilled且存在pending则该Promise的状态为pending
若返回的Promise状态为fulfilled,则其数据为Promise集合中第一个fulfilled的Promise的数据一致
-
Promise.allSettled(Promise集合)
Promise集合本质上是一个可迭代对象
返回一个Promise,当传入的Promise集合全部已决时该Promise将转变为fullfilled,否则为pending
const pro = Promise.allSettled([ Promise.resolve(1), new Promise((resolve)=>{ resolve(2); }, 1000); Promise.reject(3) ]); setTimeout(()=>{ console.log(pro); }, 1000); -
Promise.race(Promise集合)
返回一个Promise,该Promise的状态和数据会与Promise集合中第一个已决的Promise保持一致
以上的静态方法,若返回的是数组,则数组中元素的相对顺序会与集合中对应Promise的相对顺序保持一致,并不会因某个先已决就排在前面
async和await
async
async用于修饰函数,被它修饰的函数,一定返回Promise
被async修饰的函数,它原本的返回值,会成为真正返回的Promise完成后的相关数据
async function method(){
return 1;
}
console.log(method()); // Promise {<fulfilled>: 1}
注意:
-
若没有手动return,则函数默认为return undefined
-
只有async函数执行完return语句后,其返回的Promise的状态才能变为fulfilled
async function method(){} console.log(method()); // Promise {<fulfilled>: undefined}
若被async修饰的函数中抛出了错误,则该函数返回的Promise的状态为rejected,失败原因即抛出的错误
async function method(){
throw new Error("error");
console.log(1); // 不会执行
}
console.log(method()); // Promise {<rejected>: Error: error}
console.log(2); // 正常执行
注意:async修饰的函数中抛出的错误只会影响函数中处于错误之后的代码,不会影响到外部
若被async修饰的函数原本返回的就是Promise,则该函数真正返回的Promise的状态和数据最终会与其保持一致(但两者并不相同)
const pro1 = Promise.resolve(1); // Promise {<fulfilled>: 1}
async function method(){
return pro1;
}
const pro2 = method();
console.log(pro2); // Promise {<pending>}
setTimeout(()=>{
console.log(pro2); // Promise {<fulfilled>: 1}
console.log(pro1 === pro2); // false
}, 0);
await
await用于等待Promise完成,await必须使用在被async修饰的函数中
当被await等待的Promise完成后,await表达式将返回该Promise完成后的相关数据
(async ()=>{
const data = await Promise.resolve(1);
console.log(data); // 1
})();
如果await等待的不是Promise,则await会将其视为Promise,并且原本的数据就是该Promise完成后的相关数据
async function test(){
const data = await "abc"; // 相当于是await Promise.resolve("abc");
console.log(data);
}
test(); // "abc"
如果await等来的是一个rejected状态的Promise,则该await表达式将会抛出异常
async function test(){
await Promise.reject(1); // 将抛出异常
}
const pro = test();
console.log(pro); // Promise {<pending>}
setTimeout(()=>{
console.log(pro); // Promise {<rejected>: 1}
}, 0);
可以配合try catch捕获async函数中await语句所抛出的错误
注意:
-
await关键字必须直接位于async修饰的函数中
async function test(){ (()=>{ await Promise.resolve(1); // 抛出SyntaxError })(); } -
只要async函数在没执行结束时执行到了await,则同步访问该函数返回的Promise时,其状态一定为pending
async function test(){ await ...; } const pro = test(); console.log(pro); // Promise {<pending>} -
await等待的是Promise,而不是等待它后面的表达式,而所谓的await表达式是指await以及它身后的具体Promise
async function test(){ const data = await new Promise((resolve)=>{ console.log(1); resolve(2); }); console.log(data); } test(); console.log(3); // 此函数中的await表达式是指【await Promise {<fulfilled>: 1}】,而不是指【await new Promise((resolve)=>{resolve(1);});】 /** 1 3 2 */ -
await表达式之后的代码,相当于放到了onFulfilled之中,当await正常等待结束时,它们就会被放入到微队列里
async function test(){ setTimeout(()=>{ console.log(1); }, 0); const data = await Promise.resolve(2); console.log(data); } test(); console.log(3); /** 3 2 1 */async function test() { setTimeout(() => { console.log(1); }, 0); const data = await new Promise((resolve) => { setTimeout(() => { resolve(2); }, 0); }); console.log(data); } test(); console.log(3); /** 3 1 2 */
手写Promise
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
function enAsyncQueue(callback) {
// globalThis指代全局对象,浏览器环境为window,node环境为global
if (globalThis.process && globalThis.process.nextTick) { // 判断node环境
process.nextTick(callback);
} else if(globalThis.MutationObserver) {
const p = document.createElement("p");
const observer = new MutationObserver(callback);
observer.observe(p, {
childList: true
});
p.innerHTML = "1";
} else {
setTimtout(callback, 0);
}
}
function isPromise(obj) {
// 判断obj是否满足Promise A+规范
// Promise A+规范规定Promise必须是一个对象,且对象中必须包含一个名为then的属性,属性值为一个函数
return !!(obj && typeof (obj) === "object" && typeof (obj.then) === "function");
}
class MyPromise {
constructor(executor) {
this._status = PENDING;
this._value = undefined;
this._handlers = [];
try {
executor(this._resolve.bind(this), this._reject.bind(this));
} catch (err) {
this._reject(err);
console.error(err);
}
}
_changeStatus(status, value) {
if (this._status === PENDING) {
this._status = status;
this._value = value;
this._runHandlers();
}
}
_resolve(data) {
this._changeStatus(FULFILLED, data);
}
_reject(err) {
this._changeStatus(REJECTED, err);
}
_runHandlers() {
if (this._status !== PENDING) {
while (this._handlers.length) {
this._runOneHandler(this._handlers[0]);
this._handlers.shift();
}
}
}
_runOneHandler({ handler, status, resolve, reject }) {
if(this._status !== status){
return;
}
enAsyncQueue(() => {
if (typeof (handler) !== "function") {
this._status === FULFILLED ? resolve(this._value) : reject(this._value);
return;
}
try {
const result = handler(this._value);
if (isPromise(result)) {
result.then(resolve, reject);
} else {
resolve(result);
}
} catch (err) {
reject(err);
console.error(err);
}
});
}
_pushHandler(handler, status, resolve, reject) {
this._handlers.push({
handler,
status,
resolve,
reject
});
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
this._pushHandler(onFulfilled, FULFILLED, resolve, reject);
this._pushHandler(onRejected, REJECTED, resolve, reject);
this._runHandlers();
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
finally(onSettled) {
return this.then((data) => {
return MyPromise.resolve(onSettled()).then(() => {
return data;
});
}, (err) => {
return MyPromise.resolve(onSettled()).then(() => {
throw err;
})
});
}
static resolve(data) {
if (data instanceof Promise) {
// data如果是ES6的Promise,则直接返回
return data;
}
return new MyPromise((resolve, reject) => {
if (isPromise(data)) {
// data如果像Promise,则返回的Promise状态和数据与其一致
data.then(resolve, reject);
} else {
resolve(data);
}
});
}
static reject(err) {
return new MyPromise((resolve, reject) => {
reject(err);
});
}
static all(proms) { // proms应该是一个迭代器
const results = [];
let count = 0;
let fulfilledCount = 0;
return new MyPromise((resolve, reject) => {
try { // 如果传入的proms不是一个迭代器
for (const prom of proms) {
const index = count; // 记录pro在proms中的位置
count++;
// 使用MyPromise.resolve()包裹pro是为了防止pro不是Promise
MyPromise.resolve(prom).then((data) => {
results[index] = data;
fulfilledCount++;
if (fulfilledCount === count) {
resolve(results);
}
}, reject);
}
if (count === 0) {
// 传入的proms是一个空集合
resolve([]);
}
} catch (err) {
// 传入的proms不是迭代器
reject(err);
console.error(err);
}
});
}
static any(proms) {
const errors = [];
let count = 0;
let rejectCount = 0;
return new MyPromise((resolve, reject) => {
try {
for (const prom of proms) {
const index = count;
MyPromise.resolve(prom).then(resolve, (err) => {
rejectCount++;
errors[index] = err;
if (rejectCount === count) {
reject(new AggregateError(errors, "All promises were rejected"));
}
});
count++;
}
if (count === 0) {
reject(new AggregateError(errors, "All promises were rejected"));
}
} catch(err){
reject(err);
console.error(err);
}
});
}
static allSettled(proms) {
const promArr = [];
for (const prom of proms) {
promArr.push(MyPromise.resolve(prom).then((data) => {
return {
status: FULFILLED,
value: data,
}
}, (err) => {
return {
status: REJECTED,
reason: err
}
}));
}
return MyPromise.all(promArr);
}
static race(proms){
return new MyPromise((resolve, reject) => {
for (const prom of proms) {
MyPromise.resolve(prom).then(resolve, reject);
}
});
}
}