文章目录
为什么使用Promise
在ES5中使用回调函数来处理异步任务,当多个异步任务有依赖关系时(如下定时器的层层嵌套),就需要回调函数互相嵌套,当嵌套结构多了后,就出现了回调地狱的问题,难以维护
setTimeout(function () {
console.log('a1');
setTimeout(function () {
console.log('a2');
setTimeout(function () {
console.log('a3');
setTimeout(function () {
console.log('a4');
setTimeout(function () {
console.log('a5');
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
故在ES6中提供了Promise 对象,解决了以上问题,并统一了规范,提高了可读性等
Promise介绍
Promise有三种状态
pending
:等待中。属于初始状态,既没有被兑现,也没有被拒绝。fulfilled
:已兑现/已解决/成功。执行了resolve() 时,立即处于该状态,表示 Promise已经被解决,任务执行成功rejected
:已拒绝/失败。执行了 reject()时,立即处于该状态,表示 Promise已经被拒绝,任务执行失败。
Promise构造函数如下
// 1. 定义 executor 函数
function executor(resolve, reject) {
setTimeout(() => {
resolve("foo");
}, 300);
}
// 2. 把 executor 传入 Promise 构造函数
const promise1 = new Promise(executor);
//使用箭头函数则是
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("foo");
}, 300);
});
此处的resolve
reject
是形参,可以任意命名,但通常以此命名
而executor 的函数体(第一个箭头函数的函数体)中负责启动异步任务,并在恰当的时机调用resolve
或reject
如下
const coinPromise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
resolve(1);
} else {
reject('error');
}
}, 0);
});
// 使用
coinPromise
.then(val => console.log(val)) // 可能输出 1
.catch(err => console.log(err)); // 可能输出 error
then和catch也可以这样写
promise.then(onFulfilled).catch(onRejected);//这是上面的结构
//等价
promise.then(onFulfilled, onRejected);
如上是一个50%的概率输出1或error的promise实例
可以看出调用的resolve()的参数1
会被then中匿名箭头函数的参数val
接收,同理,error
对应err
并且promise的状态一旦改变,则确定了下来,不可逆,
执行机制如图
而then 方法返回一个新的 Promise,从而允许链式调用。(then返回的对象状态为待定,与当前的Promise的状态无关)
then
其中的匿名箭头函数的返回值
会被then捕获成为其新的Promise的返回值
故开头的回调可以如下修改
// 1. 先把 setTimeout 包装成返回 Promise 的工具
//计时不需要判断成功与失败,故只传resolve,构造体为setTimeout(resolve, ms)
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// 2. 链式写法,彻底消灭回调地狱
delay(1000)
.then(() => { console.log('a1'); return delay(1000); })//执行完‘ai’后会成为新的delay(1000)
.then(() => { console.log('a2'); return delay(1000); })
.then(() => { console.log('a3'); return delay(1000); })
.then(() => { console.log('a4'); return delay(1000); })
.then(() => { console.log('a5'); });
上面的链式结构没有匿名函数的参数,可以使用n记录层数作为参数
const delay = (ms, value) =>
new Promise(resolve => setTimeout(() => resolve(value), ms));
// 从 0 开始,逐层+1 n会接收resolve的value
delay(1000, 0) // 第 0 层
.then(n => { console.log('a' + n); return delay(1000, n + 1); }) // 0 → 1
.then(n => { console.log('a' + n); return delay(1000, n + 1); }) // 1 → 2
.then(n => { console.log('a' + n); return delay(1000, n + 1); }) // 2 → 3
.then(n => { console.log('a' + n); return delay(1000, n + 1); }) // 3 → 4
.then(n => { console.log('a' + n); }); // 4 → 5
如下,是抛硬币的层层嵌套,连续5次成功才会正常结束
const flip = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.5) {
resolve('success');
} else {
reject('error');
}
}, 0);
});
flip()
.then(() => { console.log('成功'); return flip(); })
.then(() => { console.log('成功'); return flip(); })
.then(() => { console.log('成功'); return flip(); })
.then(() => { console.log('成功'); return flip(); })
.then(() => { console.log('finally成功'); })
.catch(() => { console.log('失败'); });
其中的return flip();
匿名箭头函数的返回值会被then捕获成为其新的Promise的返回值
不仅如此
resolve()的参数不同,会决定对应的Promise的状态
- 如果resolve()中传入普通的值或者普通对象(包括 undefined),那么Promise 的状态为fulfilled。这个值会作为then()回调的参数。这是最常见的情况
- 如果resolve()中传入的是另外一个新的 Promise,那么原 Promise 的状态将交给新的 Promise 决定。
- 如果resolve()中传入的是一个对象,并且这个对象里有实现then()方法(这种对象称为 thenable 对象),那就会执行该then()方法,并且根据then()方法的结果来决定Promise的状态。
此处暂不涉及
故有了promise就可以更简单的处理回调函数的嵌套问题