前言
在前后端交互过程中,异步请求是必不可少的。在没有 Promise 之前,前端处理异步请求的解决方案大多是通过传入回调函数,就比如下面这样的示例代码。
function requestFn(name, successCallback, failureCallback) {
// 用 setTimeout 模拟异步请求
setTimeout(() => {
// 此处获取到结果后,外部需要知道
if (name === 'a') {
successCallback('调用成功');
} else {
failureCallback('调用失败');
}
}, 1000);
}
requestFn(
'a',
(res) => {
// 当定时器时间到了后,回调这个函数,打印如下数据
// data: 调用成功
console.log('data:', res);
},
(err) => {
console.log('err', err);
},
);
这种方式也能处理异步请求,但是存在如下缺点:
- 代码编写麻烦:自己编写这样一个
requestFn时,必须要命名好相关的参数名字,以及在合适位置进行调用回调函数 - 代码不规范:使用别人开发的
requestFn时,就像一个黑盒子一样,自己在使用时就必须去查看相关源码或文档,才能知道对应回调函数的参数怎么传,以及如何才能拿到对应的返回结果 - 回调地狱:在开发中,经常会遇到连续执行多个异步请求。当上一个请求执行成功后,才执行下一个请求,这样就会存在函数的回调参数中再嵌套一层请求的函数,非常麻烦。
于是,为了解决回调函数的这些问题,就有了新的异步处理方式:Promise。
Promise 基本概念
- Promise 是一个类,需要
new Promise()调用 - Promise 是 ES6 发布的
- Promise 用于解决异步编程问题
- Promise 可以统一规范,减少沟通成本
Promise 基本用法
使用方式:new Promise(executor)
executor 是一个函数,接收两个函数作为参数:resolve 和 reject。在请求成功时,调用 resolve。在请求失败时,调用 reject。
示例代码如下:
function fn(name) {
// 调用此函数直接返回一个 promise 对象
return new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
if (name === 'a') {
resolve('调用成功');
} else {
reject('调用失败');
}
}, 1000);
});
}
// 在这里调用后,会拿到一个 promise 对象
// 调用 promise.then 获取调用成功的结果
// 调用 promise.catch 获取调用失败的结果
const newPromise = fn('a');
newPromise
.then((res) => {
// res 调用成功
console.log('res', res);
})
.catch((err) => {
console.log('err', err);
});
Promise 三种状态
在 Promise 的不同阶段,会有如下三种状态:
- 待定 (pending)
- 已完成(fulfilled)
- 已拒绝(rejected)
- 注意:Promise 的状态一旦确定就不会再次改变,只有如下改变方式
- pending -> fulfilled
- pending -> rejected
示例代码如下:
new Promise((resolve, reject) => {
// FIXME:在执行 resolve 和 reject 回调之前,Promise 状态都一直是 pending
let a = 1;
console.log('a', a);
// 调用 resolve 后,Promise 状态就会从 pending 变为 fulfilled
resolve();
// 因为 Promise 的状态一旦改变,就已经确定了
// 这后续执行的代码,Promise 状态也会是 fulfilled
let b = 2;
console.log('b', b);
// 即使这里调用 reject 也不会修改 Promise 状态
// reject()
}).then(
(res) => {
// FIXME: 到了这个地方之后,Promise 状态就变成 fulfilled
console.log(res);
},
(err) => {
// FIXME: 到了这个地方之后,Promise 状态就变成 rejected
console.log(err);
},
);
resolve 参数详解
resolve 参数有三种类型:
- 普通数据类型:字符串、普通对象 等
const promise1 = new Promise((resolve, reject) => {
resolve(111);
});
promise1.then((res) => {
// res 111
console.log('res', res);
});
- promise 对象
const promise2 = new Promise((resolve, reject) => {
// 当 resolve 中的参数为 promise 对象时,则当前这个 new Promise 的状态将会由此传入的这个 promise 对象的状态决定
// 我觉得就相等于是一个拦截操作,权限移交的操作
resolve(
new Promise((resolve, reject) => {
// resolve('promise data');
// 这里调用就 reject 则,后续就需要捕获错误
reject('promise err');
}),
);
});
promise2.then(
(res) => {
// res promise data
console.log('res', res);
},
(err) => {
// err promise err
console.log('err', err);
},
);
-
Thenable 对象 MDN 解析 Thenable 对象
什么是 Thenable 对象?
- 一个对象中有 then 方法,且该方法有两个回调函数作为参数。第一个参数用于在已完成时调用,第二个参数用于在已结束时调用。
- 其实这个地方和
new Promise((resolve, reject) => {})是一致的。
const thenableObj = {
then(resolve, reject) {
resolve('thenable resolve');
// reject('thenable reject');
},
};
const promise3 = new Promise((resolve, reject) => {
// 传入 thenable 的情况和 promise 情况一致
// 当前这个 new Promise 的状态,由 resolve 中传入的 thenable 的状态决定
// 相当于就是拦截操作,权限移交的操作
resolve(thenableObj);
});
promise3.then(
(res) => {
// res thenable resolve
console.log('res', res);
},
(err) => {
// err thenable reject
console.log('err', err);
},
);
Promise 对象方法
- Promise 对象方法是需要先
new Promise之后,调用对象的方法
then
(1) 可链式调用
const promise = new Promise((resolve, reject) => {
resolve('promise');
});
promise.then(res1 => console.log(res1)).then(res2 => console.log(res2))
(2) 可直接在 then(resCallback, errCallback),可直接在 errCallback 中捕获异常错误
- 缺点:代码不够清晰
const promise = new Promise((resolve, reject) => {
resolve('promise');
});
promise.then(res => {
console.log(res)
},
err => {
console.log(err)
})
(3) promise.then 的返回值
- promise.then 会返回一个 Promise
- promise.then 返回值,一共有如下三种情况
(3.1) 返回普通数据
- 内部为 Promise 包裹了一下,然后走的 resolve 流程
const promise = new Promise((resolve, reject) => {
resolve('promise');
});
promise
.then((res) => {
return 'aaa';
})
.then((res) => {
// then 普通返回值 aaa
console.log('then 普通返回值', res);
});
(3.2) 返回值为 Promise
- 后续的调用的状态由这个返回的 Promise 内部的状态决定
const promise = new Promise((resolve, reject) => {
resolve('promise');
});
promise
.then((res) => {
return new Promise((resolve, reject) => {
reject('错误');
});
})
.then(
(res) => {
console.log('res', res);
},
(err) => {
// err 错误
console.log('err', err);
},
);
(3.3) 返回值为 thenable
- 后续的调用的状态由这个返回的 thenable 内部的状态决定
promise
.then((res) => {
const obj = {
then(resolve, reject) {
reject('thenable 错误');
},
};
return obj;
})
.then(
(res) => {
console.log('res', res);
},
(err) => {
// err thenable 错误
console.log('err', err);
},
);
catch
(1) 直接捕获 promise 异常
const promise = new Promise((resolve, reject) => {
reject('promise 错误');
});
promise.catch((err) => {
// err promise 错误
console.log('err', err);
});
(2) 链式调用捕获 promise 异常
- 此处 .catch,优先捕获 promise 的错误
- 如果 promise 是正常的,才开始捕获 promise.then 中的错误
- 也就是先来后到的原则
- 个人感悟:我这几天写这个代码的时候,分析的时候发现,我现在只要理清楚代码的逻辑就可以了,我不管用什么比喻的方式来阐述这个道理,只要能讲清楚就可以了
const promise = new Promise((resolve, reject) => {
reject('promise 错误');
});
promise
.then((res) => {
return new Promise((resolve, reject) => {
reject('promise.then 错误');
});
})
.catch((err) => {
// err promise 错误
console.log('err', err);
});
finally
- 不管是 fulfilled 和 rejected 最后都会调用 finally 方法
const promise = new Promise((resolve, reject) => {
reject('promise 错误');
});
promise
.then((res) => {
console.log('res', res);
})
.catch((err) => {
// err promise 错误
console.log('err', err);
})
.finally(() => {
// finally 调用
console.log('finally 调用');
});
Promise 类方法
- Promise 类方法是直接调用 Promise 类上面的方法即可
resolve
用法:Promise.resolve()
const obj = {
name: 'aaa',
};
// 1. 入参为普通类型
const p1 = Promise.resolve(obj);
p1.then((res) => {
// res1 { name: 'aaa' }
console.log('res1', res);
});
// 等价于
// 说白了就是语法糖,然后 Promise 内部处理过,提供了一个简便的方式
const p2 = new Promise((resolve, reject) => {
resolve(obj);
});
p2.then((res) => {
// res2 { name: 'aaa' }
console.log('res2', res);
});
// 2. 入参为 promise
const p3 = new Promise((resolve, reject) => {
reject('promise');
});
const p4 = Promise.resolve(p3);
p4.then(
(res) => {
console.log('res', res);
},
(err) => {
// err promise
console.log('err', err);
},
);
// 3. 入参为 thenable
const thenObj = {
then(resolve, reject) {
reject('thenable');
},
};
const p5 = Promise.resolve(thenObj);
p5.then(
(res) => {
console.log('res', res);
},
(err) => {
// err thenable
console.log('err', err);
},
);
reject
用法:Promise.reject()
- Promise.reject 中的状态,始终会是 rejected 状态,和传入的类型无关
const p1 = new Promise((resolve, reject) => {
resolve('测试');
});
const p2 = Promise.reject(p1);
p2.then(
(res) => {
console.log('res', res);
},
(err) => {
// err Promise { '测试' }
console.log('err', err);
},
);
all
用法:Promise.all(promiseList)
- 要么 promiseList 所有状态都是 fulfilled
- 要么 promiseList 中一旦有一个 reject 中断后,则走 catch 方法
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('p1 成功');
}, 1000);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('p2 成功');
// FIXME: 一旦其中有一个 reject 中断之后,则直接将此 reject 的数据返回
reject('p2 错误');
}, 2000);
});
const pList = Promise.all([p1, p2]);
pList.then(
(res) => {
// pList 中所有 promise 都为 fulfilled 则走此处
// 打印:res [('p1 成功', 'p2 成功')];
console.log('res', res);
},
(err) => {
// pList 中一旦其中有一个 reject 中断之后,则走此处
// 打印:err p2 错误
console.log('err', err);
},
);
allSettled
用法:Promise.allSettled(promiseList)
- 等待所有状态都敲定后,再走 then 方法
- fulfilled 状态:
{ status: 'fulfilled', value: 111 } - rejected 状态:
{ status: 'rejected', reason: 222 }
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 100);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(222);
}, 200);
});
const pList = Promise.allSettled([p1, p2]);
pList.then((res) => {
// res [({ status: 'fulfilled', value: 111 }, { status: 'rejected', reason: 222 })];
console.log('res', res);
});
race
用法:Promise.race(pList)
- 获取第一个 resolve 的数据
- 如果 reject 比 resolve 先获取,则走 catch 就结束了
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(111);
}, 100);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(222);
}, 200);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(333);
}, 30);
});
const pList = Promise.race([p1, p2, p3]);
pList.then(
(res) => {
console.log('res', res);
},
(err) => {
// err 333
console.log('err', err);
},
);
any
用法:Promise.any(promiseList)
- 以获取到第一个 resolve 为结束的标志,reject 不会终止执行效果
- 如果全都是 rejected 则打印如下:
err [AggregateError: All promises were rejected] { [errors]: [ 333 ] }
const p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 100);
});
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(222);
}, 200);
});
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject(333);
}, 30);
});
Promise.any([p1, p2, p3]).then(
(res) => {
// res 222
console.log('res', res);
},
(err) => {
console.log('err', err);
},
);
参考资料
- coderwhy 深入 JavaScript 高级语法课程
- MDN Promise 用法
- ECMA2015 Promise 发布