一、Promise的引入
首先,我们先来看个例子。
function xq() {
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('张三去相亲!');
resolve('相亲成功!')
},2000)
})
}
function marry() {
return new Promise((resolve, reject) =>{
setTimeout(() => {
console.log('张三结婚了!');
resolve()
},1000)
})
}
function baby() {
setTimeout(() => {
console.log('小张出生了!');
},500)
}
xq()
marry()
baby()
完犊子了!我代码里明明是先调用相亲函数,再调用结婚函数,最后才是生娃的,怎么输出顺序乱套了呢?
我们再来看一个例子。
console.log('1');
console.log('2');
setTimeout(() => {
console.log('3')
},1000)
console.log('4');
console.log('5');
输出结果是
12453,为啥不是按照顺序12345这样来输出呢?我代码里明明是按这个顺序写的呀,有什么办法能解决这个问题呢?诶,试一下回调函数吧!
setTimeout(function () {
console.log('1');
setTimeout(function () {
console.log('2');
setTimeout(function () {
console.log('3');
setTimeout(function () {
console.log('4');
setTimeout(function () {
console.log('5');
})
})
})
})
})
输出结果是
12345,好了,这下输出顺序的问题倒是解决了,但是也成俄罗斯套娃了,如果我要输出更多东西呢?你不觉得这代码多少有点不优雅吗?
传统的回调函数方式可能会导致代码嵌套层次深、难以维护和理解,像这样回调函数里面嵌套回调函数,我们称为“回调地狱”(Callback Hell)。本文将介绍现代JavaScript中的一种异步编程技术Promise,尝试摆脱回调地狱,写出更加优雅和可维护的异步代码。
二、什么是 Promise?
Promise 是 JavaScript 提供的一种用于处理异步操作的对象,比传统的解决方案——回调函数和事件——更合理和更强大。它可以代表一个异步操作的最终完成或失败,并可以将其结果传递给相应的处理函数。
Promise 有三个状态:
Pending(进行中): 初始状态,表示异步操作正在进行中,尚未完成。Fulfilled(已完成): 表示异步操作已经成功完成,并且有一个返回值。Rejected(已失败):表示异步操作已经失败,可能由于某种错误。
一旦 Promise 进入了 Fulfilled 或 Rejected 状态,就被称为已 resolved(已定型),状态将不再改变,会一直保持这个结果。可以通过 .then() 或 .catch() 方法来处理 Promise 的结果。
三、Promise的一些常用API
1.Promise.resolve()和Promise.prototype.then()
首先我们来看一段代码,如下所示:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
},2000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
},1000)
})
}
a().then(() => {
b();
})
执行结果是输出
aaa,但是我们的本意是想使得b函数在a函数之后被执行,可是这里并没有执行b函数,为什么呢?我们接着再看一段代码:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
let res = '我是a函数里的resolve信息';
resolve(res);
},2000)
})
}
function b(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
console.log(data);
},1000)
})
}
a().then((res) => {
console.log(res);
b(res);
})
then方法可以为Promise实例添加状态改变时的回调函数,定义在原型对象Promise.prototype上。如果有resolve函数的调用,then里面的回调函数就会被触发,否则不会。所以上面的例子中之所以没有执行b函数,是因为没有调用resolve函数。
如果a函数里有信息想拿给b函数去使用,可以通过resolve函数传递参数,这样就能很方便的把一个函数内部的变量传递给另外一个函数使用。
2.Promise.reject()和Promise.prototype.catch()
我们继续来看一段代码,如下所示:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
reject('我是a函数里的reject信息')
},2000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
},1000)
})
}
a().then((res) => {
console.log(res);
b();
})
好家伙,不仅
b函数没有执行,还报错了!这是为什么呢?
很多时候我们在接口请求时要用到Promise对象,接口请求可能成功也可能失败,成功就用resolve处理结果,失败就用reject处理结果。因为这里调用的是reject函数,在catch里执行的,而不是在then里执行的,catch这个方法就是专门用来捕获错误。resolve对应then,reject对应catch。
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
reject('我是a函数里的reject信息')
},2000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
},1000)
})
}
a()
.then((res) => {
console.log('then: ', res);
b();
})
.catch((err) => {
console.log('catch: ', err);
})
3.Promise.prototype.finally()
finally()方法用于指定不管Promise对象最后状态如何,都会执行的操作,不管此时是then和catch中的哪一个执行,finally一定会执行。主要用于在处理完Promise结果后执行一些清理操作,例如关闭文件或释放资源。下面的代码中省略了之前相同的部分,节省篇幅。
a()
.then((res) => {
console.log('then: ', res);
b();
})
.catch((err) => {
console.log('catch: ', err);
})
.finally(() => {
console.log('finally里的操作执行啦!');
})
4.Promise.all()
Promise.all() 方法接收一个包含多个 Promise 对象的数组,并返回一个新的 Promise 对象,该对象在数组中的所有 Promise 对象都变为已解决状态时才会变为已解决状态,结果值是一个包含所有 Promise 对象结果值的数组,顺序与输入数组一致。
a().all()
报错,输出
a().all() is not a function,我们不能直接a().all(),实例对象是不能直接调用all方法的,因为all方法没有挂在原型上,没被隐式继承,实例对象是访问不到的。正确写法如下:
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
resolve('我是a函数里的resolve')
},1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
resolve('我是b函数里的resolve')
},2000)
})
}
function c() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ccc');
},500)
})
}
Promise.all([a(), b()]).then(c)
c依赖a和b的执行结果,只有等a和b都执行完后才执行。
5.Promise.race()
Promise.race()方法接收一个包含多个 Promise 对象的数组,并返回一个新的 Promise 对象,该对象在数组中的任何一个 Promise 对象变为已解决或已拒绝状态时就会变为相应的状态,并使用第一个解决或拒绝的 Promise 对象的结果或原因作为结果。
Promise.race([a(), b()]).then(c)
race有比赛、竞争的意思,当发送接口请求时,有可能是a先拿到数据结果,也有可能b先拿到数据结果,这个跟HTTP、TCP、UDP有关,反正c不知道谁先谁后,所以就调用race方法,有结果执行出来的第一时间就拿到该结果。这里a需要花1秒,b需要2秒,a更快,所以c跟在a之后执行。
6.Promise.any()
Promise.any()跟Promise.race()方法很像,只有一点不同,就是Promise.any()不会因为某个Promise变成rejected状态而结束,必须等到所有参数Promise变成rejected状态才会结束。
function a() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('aaa');
reject('我是a函数里的resolve')
},1000)
})
}
function b() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('bbb');
reject('我是b函数里的resolve')
},2000)
})
}
function c() {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('ccc');
},500)
})
}
Promise.any([a(), b()]).then(c)
此时,
a函数和b函数都是reject,都执行失败,所以then里的c函数就不执行,程序报错,c函数依赖前面函数的执行结果,但凡a函数和b函数中有一个执行成功,c函数就会执行成功。any就是任何的意思嘛,有一个就行。
四、Promise的实际应用
现在让我们通过一个实际的例子来展示 Promise 的用法。假设我们有一个简单的异步操作,从服务器获取用户信息,并在获取成功后进行一些处理。下面是使用 Promise 的示例代码:
// 模拟从服务器获取用户信息的异步操作
function getUserInfoFromServer(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟异步请求成功
const userInfo = {
id: userId,
name: "zhangsan",
age: 20
};
resolve(userInfo); // 将结果传递给成功处理函数
}, 1000);
});
}
// 使用 Promise 处理异步操作的结果
getUserInfoFromServer(666)
.then(userInfo => {
console.log("用户信息:", userInfo);
// 进行其他处理
})
.catch(error => {
console.error("获取用户信息失败:", error);
});
在上面的示例中,我们通过 getUserInfoFromServer()函数模拟了一个异步操作,并使用 Promise 处理了成功的结果。如果异步操作成功,then()方法的回调函数将被调用,并传递用户信息作为参数;如果异步操作失败,catch()方法的回调函数将被调用,并传递错误信息作为参数。
下面是一个使用Promise.all处理动态数量的Promise的例子:
// 动态生成 Promise 数组
function generatePromises(num) {
const promises = [];
for (let i = 1; i <= num; i++) {
promises.push(
new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
if (i % 2 === 0) {
resolve(`Promise ${i} 已完成`);
} else {
reject(`Promise ${i} 已失败`);
}
}, Math.random() * 1000);
})
);
}
return promises;
}
const num = 5; // 动态生成 5 个 Promise 对象
const promises = generatePromises(num);
// 使用 Promise.all 处理动态数量的 Promise
Promise.all(promises)
.then(results => {
console.log(results); // 所有 Promise 都成功解决时的结果数组
})
.catch(error => {
console.error(error); // 第一个被拒绝的 Promise 的错误值
});
在上面的代码示例中,generatePromises函数用于动态生成一定数量的Promise对象,每个Promise对象都可能成功解决或被拒绝。然后,将这些生成的Promise对象作为参数传递给Promise.all方法,Promise.all方法会返回一个新的Promise,在所有输入的Promise对象都成功解决时才会被解决,如果任何一个输入的Promise对象被拒绝,则返回的Promise也会被拒绝,并将第一个被拒绝的Promise的错误值作为拒绝原因。
五、结语
Promise 是 JavaScript 中处理异步操作的一种强大工具,它可以使异步编程更加优雅和简洁。在实际的开发中,可以进一步深入学习 Promise,并结合实际项目中的异步操作来灵活运用,合理地运用 Promise,从而写出更加优秀的 JavaScript 代码,有效地提高代码的可读性、可维护性和健壮性。
感谢你阅读这篇文章,如果你觉得写得还行的话,不要忘记点赞、评论、收藏哦!如有问题欢迎大家指正,祝大家生活愉快!