目录
异步任务
在js
中,有些任务并不是立即执行
的,如setTimeOut
或者发送网络请求
等等
这些任务或多或少的延时
,而这些延时
都可能会导致js代码实际的执行顺序和我们预计的不一样
有些时候我们也会需要这些任务完成后的返回值
来进行进一步的操作
如下面代码所示
function ajax(url) {
setTimeout(function () {
if (url == "http://yes") {
return true
} else {
return false
}
}, 1000)
}
var data = ajax("http://yes")
console.log(data)
这段代码中我们模拟了一个网络请求
,希望data
能接受到服务器返回的结果,可打印的结果却是undefined
回调地狱
为了保证代码的执行顺序,也为了保证我们能拿到需要的返回值,解决以上问题的方法就是使用回调函数
我们将以上的代码改写一下
function ajax(url, success, fail) {
setTimeout(function () {
if (url == "http://yes") {
success("请求成功")
} else {
fail("请求失败")
}
}, 1000)
}
ajax("http://yes", function (res) {
console.log(res)
}, function (err) {
console.log(err)
})
结果
我们会在1秒之后拿到结果
现在,我们的需求变成了需要监听页面中的一个按钮,当用户点击了这个按钮的1秒后发送网络请求
代码就变成了下面这样
function ajax(url, success, fail) {
setTimeout(function () {
if (url == "http://yes") {
success("请求成功")
} else {
fail("请求失败")
}
}, 1000)
}
document.querySelector("button").addEventListener("click", function () {
setTimeout(function () {
ajax("http://yes", function (res) {
console.log(res)
}, function (err) {
console.log(err)
})
}, 1000)
})
这种回调函数嵌套回调函数的做法被称之为回调地域
,为了能保证任务的执行顺序,以上代码都是通过硬编码
的形式解决
我们将setTimeOut
硬编码到click
中,再将ajax
硬编码到setTimeOut
中,以此类推
这种硬编码
所带来的无法复用
以及难以处理每个步骤中发生的异常情况
的问题是回调地狱
真正问题所在
Promise
在ES6
之后规定了一种新的编程范式Promise
,它可以很轻松的解决以上问题
通过new
创建Promise
对象时,我们需要传入一个回调函数
,这个回调函数
被称之为executor
这个回调函数
会立即执行,并且在传入2个回调函数
,resolve
和reject
当调用resolve
方法时会调用Promise
对象的then
方法传入的回调函数
当调用reject
方法时就会调用Promise
对象的catch
方法传入的回调函数
Promise的三种状态
var promise = new Promise((resolve, reject) => {
resolve("success")
reject("fail")
})
promise.then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
})
在Promise
的使用过程中会出现三种状态
- fulfilled
也叫做兑现状态
,位于这种状态时表示Promise
中的操作成功完成
,即调用了resolve
方法 - rejected
也被叫做拒绝状态
,位于这种状态时表示Promise
中的操作失败
,即调用了reject
方法 - pending
也被叫做初始状态
,这种状态意味着此时Promise
既没有兑现也没有拒绝
,当Promise
没有调用resolve
和reject
方法时Promise
位于这种状态
值得注意的是Promise
可以从pending
状态变成fulfilled
和rejected
状态
但Promise
无法从fulfilled/rejected
变为pending
和rejected/fulfilled
状态
这意味着如果我们在Promise
中同时调用了resolve
和reject
方法,那么只会执行最先调用的那个方法
剩下的方法并不是无法执行
,而是状态被锁死无法改变
如上面的代码所示,控制台结果如下
resolve传入值
- 传入一个
普通对象
或一个普通值
这个值将会作为then
方法的回调函数的参数
,并且也会将这个Promise
的状态置为fulfilled
- 传入一个
Promise
这个新的Promise
的状态将会决定原来Promise
的状态 - 传入一个
对象
,对象中有then方法
也叫做thenable
对象,执行这个then
方法,并根据then
方法的结果来决定Promise
的状态
Promise的实例方法
Promise
的实例方法
具体有以下三个
then
then
方法接收两个参数
,一个是成功的回调函数
,一个是失败的回调函数
var promise = new Promise((resolve, reject) => {
resolve("success")
reject("fail")
})
promise.then((res) => {
console.log(res)
}, (err) => {
console.log(err)
})
这种写法等价于上面演示的写法
一个Promise
的then
方法可以多次调用
var promise = new Promise((resolve, reject) => {
resolve("success")
})
promise.then((res) => {
console.log("res1", res)
})
promise.then((res) => {
console.log("res2", res)
})
结果如下
then的返回值
then
方法也具有返回值
,他会返回一个Promise
因为返回的是一个Promise
所以能进行链式调用
而如果想要继续调用接下来的then
方法就需要在这个Promise
中调用resolve
方法
resolve
方法可以传入参数
,这里的参数
就是最开始的then
方法的回调函数的返回值
有以下三种情况
-
回调函数返回一个
普通值
var promise = new Promise((resolve, reject) => { resolve("success") }) promise.then((res) => { console.log(res) return "aaa" }).then((res) => { console.log(res) })
相当于
then
方法返回一个Promise
,Promise
调用了resolve
方法,resolve
方法将aaa
作为参数传递
给了下一个then
方法
控制台结果如下
-
回调函数返回一个
Promise
var promise = new Promise((resolve, reject) => { resolve("success") }) promise.then((res) => { console.log(res) return new Promise((resolve, reject) => { resolve("aaa") }) }).then((res) => { console.log(res) })
如果返回的是一个
Promise
的会先等新的Promise
调用resolve
之后将resolve
的结果传递给下一个then
控制台结果同上 -
回调函数返回一个
含有then方法的对象
var promise = new Promise((resolve, reject) => { resolve("success") }) promise.then((res) => { console.log(res) return { then: function (resolve) { resolve("aaa") } } }).then((res) => { console.log(res) })
如果返回的是一个
thenable
对象的话会等resolve
之后将对应的值
返回给下一个then
控制台结果同上
返回Promise的状态
在运行then
中的回调函数
时Promise
的状态为pending
在回调函数返回一个结果
时Promise
的状态为fulfilled
在then
方法抛出一个异常
时Promise
的状态为rejected
catch
catch
方法和then
方法一样可以多次调用
var promise = new Promise((resolve, reject) => {
reject("error")
})
promise.catch((res) => {
console.log("res1", res)
})
promise.catch((res) => {
console.log("res2", res)
})
控制台结果如下
catch的返回值
catch
方法也会返回一个Promise
,所以我们也能在catch
后面链式调用then
或catch
值得注意的是,catch
的回调执行完后返回的Promise
默认状态为fulfilled
即会在catch
调用完后在接下来的链式调用中跳过catch
,调用then
如果想要调用接下来的catch
则需要抛出一个异常
var promise = new Promise((resolve, reject) => {
reject("error")
})
promise.catch((res) => {
console.log("res1", res)
throw new Error("error")
}).catch((res) => {
console.log("res2", res)
return "success"
}).catch((res) => {
console.log("res3", res)
return "error"
}).then((res) => {
console.log("res4", res)
})
控制台结果如下
finally
finally
是在ES9
中新增的一个方法
表示无论Promise
对象无论变成fulfilled
还是rejected
状态,最终都会执行
的代码
finally方法不接收参数
var promise = new Promise((resolve, reject) => {
resolve("success")
})
promise.catch((res) => {
console.log("res1", res)
}).then((res) => {
console.log("res2", res)
}).finally(() => {
console.log("res3")
})
控制台结果如下
Promise的类方法
Promise
除了有实例方法
之外还有类方法
resolve
resolve
方法会将一个现成的内容转换成Promise来使用
var promise = Promise.resolve("success")
promise.then((res)=>{
console.log(res)
})
相当于
var promise = new Promise((resolve, reject) => {
resolve("success")
})
promise.then((res) => {
console.log(res)
})
控制台结果如下
reject
和resolve
类似,只不过会调用reject方法
var promise = Promise.reject("error")
promise.catch((res) => {
console.log(res)
})
相当于
var promise = new Promise((resolve, reject) => {
reject("error")
})
promise.catch((res) => {
console.log(res)
})
控制台结果如下
all
all
方法会将多个Promise
包裹成一个新的Promise
,新的Promise
状态由包裹的所有Promise决定
当包裹的所有Promise
的状态都为fulfilled
时,新Promise
的状态为fulfilled
,并且会将包裹的所有Promise
的返回值组成一个数组
返回
如果包裹的Promise
中有一个状态为reject
时,新Promise
状态为reject
,并且会将第一个reject
的返回值
返回
var p1 = new Promise((resolve, reject) => {
resolve("p1 success")
})
var p2 = new Promise((resolve, reject) => {
resolve("p2 success")
})
var p3 = new Promise((resolve, reject) => {
reject("p3 error")
})
var p4 = Promise.all([p1, p2])
p4.then((res) => {
console.log(res)
})
var p5 = Promise.all([p2, p3])
p5.catch((res) => {
console.log(res)
})
控制台结果如下
allSettled
allSettled
方法和all
方法类似,但不同的是allSettted
方法会等待所有Promise有结果后才会有最终的状态
,并且最终的状态一定为fulfilled
var p1 = new Promise((resolve, reject) => {
resolve("p1 success")
})
var p2 = new Promise((resolve, reject) => {
resolve("p2 success")
})
var p3 = new Promise((resolve, reject) => {
reject("p3 error")
})
var p4 = Promise.allSettled([p1, p2, p3])
p4.then((res) => {
console.log(res)
})
控制台结果如下
其中status
为Promise
的状态,value
为这个Promise
的返回值
race
race
同样将多个Promise
组合成一个新Promise
在这些Promise
中如果有一个结果
,新Promise
的状态就由这个有结果的Promise
决定
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 success")
}, 2000);
})
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2 success")
}, 3000);
})
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p3 error")
}, 100);
})
var p4 = Promise.race([p1, p2, p3])
p4.then((res) => {
console.log(res)
}).catch((res) => {
console.log(res)
})
控制台结果如下
any
和race
方法相似,不同的地方在于any
方法会一直等到第一个fulfilled状态
的Promise
出现再来决定新Promise
的状态
如果所有的Promise
均为reject
就会报一个AggregateError
的错误
var p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1 success")
}, 2000);
})
var p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2 success")
}, 3000);
})
var p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("p3 error")
}, 100);
})
var p4 = Promise.any([p1, p2, p3])
p4.then((res) => {
console.log(res)
})
var p5 = Promise.any([p3])
p5.catch((res) => {
console.log(res)
})
控制台结果如下