目录
Promise的出现把JavaScript的异步函数的功能大大提升,并且解决了回调地狱的问题。本文将从promise的核心出发,仔细分析promise对于一个新手来说的意义和用法。
1. 我们为什么需要promise?
首先,我们要先清楚promise出现的意义。想象一个场景,我们需要先执行A,再执行B,再执行C。
但是A,B,和C的执行都需要时间,如果我们直接写的话,下面的事件并不会等待前面的执行才开始执行,而且,很多时候我们需要使用A的结果,再利用A的结果来调用B,以此类推。
传统上,我们会使用回调函数来解决此类问题
firstFunction(args, function () {
secondFunction(args, function () {
thirdFunction(args, function () {
// And so on…
});
});
});
但是你也应该会发现,一旦我们有好几个函数需要有这种前后执行关系,这段代码就会变得很乱很难理解,这种多个函数互相嵌套的多层回调就是我们常说的回调地狱,然而这也是promise大放异彩的时候了。
Promise的出现就是为了解决异步操作中回调地狱的问题。一个promise构造函数接受两个参数:resolve()和reject()
他们是两个函数,分别决定promise的结果是成功与否,
如果调用resolve,那么代表代码执行成功,如果调用reject,则代表失败
首先,我们可以调用Promise的构造函数来创建一个新的promise对象:
let user = 'happy'
let goodPromiseArticle = new Promise((resolve, reject) => {
setTimeout(() => {
if (user == 'happy') {
resolve('用户喜欢本文章')
} else if (user == 'sad') {
reject('用户离开了')
}
}, 1000);
})
如你所见,resolve和reject分别可以接受一个参数,当然,如果你没有传任何参数,promise完全可以正常执行,那我们为什么有时候需要传参数呢?
这就牵扯到我们之前说的,后面执行的函数需要之前函数的返回值作为参数。一个promise对象可以执行.then方法。
then方法让回调地狱变得可读了许多
then方法一定会在promise执行结束后才执行,并且接受两个函数。
第一个函数可以接受到resolve函数中的参数的值,第二个接受reject中参数的值
如果promise执行了resolve,then就会执行第一个函数,如果执行了reject,就会执行第二个函数
因此,我们也可以了解到promise其实有三种状态
当一个promise执行还没有执行完毕的时候,他的结果将是pending(待定状态)
当一个promise执行了resolve,我们就会说这个promise的状态为fulfilled(操作成功)
当一个promise执行了reject,我们就会说这个promise的状态为rejected(操作失败)
注意:当promise的状态变为成功和失败后,是不能再改变状态的
goodPromiseArticle.then(
function (value) {
console.log(value)
},
function (value) {
console.log(value)
}
) // *1秒钟后* 用户喜欢本文章
就这样,我们通过promise来判断用户喜不喜欢改文章,并调用了resolve()。因为promise的状态为fulfilled,then将调用第一个函数并且得到resolve的函数作为参数value。
2. Promise的链式写法
then函数会返回一个新的promise,因此,你可以使用链式写法链接then函数。每一个then中promise的结果会交给下一个then来处理,由此形成一个和回调地狱情况类似但可读性完胜回调的异步解决方案
promise.then(
// handlePromise
).then(
// handlePromise
).then(
// handlePromise
).then(
// handlePromise
)
像我们之前所说,then函数接受两个函数,分别解决resolve和reject的结果,但如果每个then里面都有两个函数,代码量会变得特别大。
所幸的是,但如果then中没有第二个处理reject的函数的话,链式调用就会直接进行下一轮操作。也就是说,如果我们省略then函数中所有的第二个处理reject的函数,那我们就可以在链式调用的最后写一个catch,来处理所有链式调用中发生的所有错误
promise.then(
// handleResolvedA
).then(
// handleResolvedB
).then(
// handleResolvedC
).then(
// handleResolvedD
).catch(err => console.log(err)) // 统一错误处理
Promise除了可以调用resolve,reject, 和then以外,其实还有好几种其他的方法。比如Promise.all(), Promise.race()等等。但是95%的情况下,resolve,reject,then,catch就完全够我们使用了,这是MDN的promise的文档,在熟练本文的基础上,可以观看MDN的文章来继续扩充对Promise的认识和了解。
3. Promise的实际应用
Promise在实际项目中最常用的场景就是发送http请求,因为http请求和相应本身为异步,所以我们大多时候需要先获取数据,再对这些数据进行操作,比如渲染到页面或者更改数据格式。因此,我们需要借助promise的力量,帮我们先拿到数据,再进行一系列的操作。
先看下面这个简单场景,如果我们需要先得到1+2的值,再把这个值乘以3,最终将它赋值给num:
let num = 0
let changeNumber = new Promise((resolve, reject) => {
resolve(1 + 2)
})
changeNumber
.then(function (result) {
console.log(result)
return Promise.resolve(result * 3)
})
.then(function (result) {
console.log(result)
return Promise.resolve()
})
then方法一定会返回一个新的promise,尽管你没有明确返回一个promise。比如我们刚刚的例子,你完全也可以这样写,因为then会隐形地为你返回一个promise结果
changeNumber
.then(function (result) {
console.log(result)
return result * 3
})
.then(function (result) {
console.log(result)
})
当然,以上这个例子完全不需要大动干戈地使用promise来解决, 那么接下来我们看一个promise在实际应用中的案例
我们用js自带的fetch api来发送一个请求来获取一个随机狗狗图片,并把它渲染到页面上
const img = document.querySelector('.img')
fetch('https://dog.ceo/api/breeds/image/random', {
method: 'GET'
})
.then(function (res) {
return Promise.resolve(res.json())
})
.then(function (data) {
img.src = data.message
console.log(data)
})
.catch(function (err) {
console.log(err)
})
首先,我们先发送一个http请求
得到结果后,我们将结果的响应体的data取出来,通过res.json()
然后,我们利用data的值,将狗狗的照片渲染到页面上
最后,我们使用catch统一处理前面的所有代码中可能发生的错误
在熟练掌握promise的链式写法后,我们其实不需要这么多代码,下面的这种箭头函数的简写才是我们在项目中真正会经常写的写法:
fetch('https://dog.ceo/api/breeds/image/random', {
method: 'GET'
})
.then(res => res.json())
.then(data => {
img.src = data.message
console.log(data)
})
.catch(err => console.log(err))
4. async await和promise
尽管promise特别好用,很多人还是觉得这种需要通过then的链式写法看起来不舒服。他们还是更喜欢之前同步函数的写法和长相。因此,在后续的ES更新中,我们多了一个语法糖功能,叫做async和await。
async function name(params) {
await statements
}
async函数允许在其内部使用await关键字。他的存在基本就是为了能写出长得像同步函数的异步函数,而非可以使用promise的then链式写法。
如同then一样,当你使用await关键字的时候,下面的代码会进入一个暂停状态,直到await后面的代码执行结束后,函数才继续执行。
注意!你可以随意声明async函数,但是await只能在async函数中使用
拿我们上面的promise的http请求举例,我们完全可以把它转换成一个async函数:
// Promise写法
fetch('https://dog.ceo/api/breeds/image/random', {
method: 'GET'
})
.then(res => res.json())
.then(data => {
img.src = data.message
console.log(data)
})
.catch(err => console.log(err))
// async写法
async function fetchData() {
let response = await fetch('https://dog.ceo/api/breeds/image/random', {
method: 'GET'
})
let data = response.json()
console.log(data)
img.src = data
}
fetchData()
看到这里你也许会问:我怎么没有看见catch?那我们如何对错误进行处理呢?
其实很简单,我们只需要把所有代码使用try,catch的方式包裹起来,就能轻松地捕捉几乎所有代码处理中会发生的错误
async function fetchData() {
try {
let response = await fetch('https://dog.ceo/api/breeds/image/random', {
method: 'GET'
})
let data = response.json()
console.log(data)
img.src = data
} catch (err) {
console.log(err)
}
}
fetchData()