Promise 最大的好处是在异步执行的过程中,将执行异步代码和处理结果清晰地分离了。
new Promise((resolve,reject)=> {
// 执行异步代码
setTimepot(()=> {
resolve('hello js')
},1000)
}).then(res => {
// 处理结果代码
console.log(res)
})
Promise 基本用法
我们用一个定时器来模拟异步事件:
- 假设下面的
data是从网络上 1 秒后请求的数据(执行异步代码) console.log就是我们的处理方式(处理结果代码)
new Promise((resolve,reject)=> {
setTimeout(function() {
// 下面两个只会抛出一个
resolve('Hello World')
reject('Error Data')
},1000)
// 下面也只会捕获一个
}).then(data => {
console.log(data) // Hello World
}).catch(error => {
console.log(error) // Error Data
})
解读:
- new Promise 创建了一个 Promise 对象
(resolve,reject)=>{}是一个执行器函数- 执行器函数的参数
resolve和reject也是两个函数,我们会根据请求数据的成功和失败来决定调用哪一个。
executor
Promise 构造函数接受一个 executor 函数作为参数,该函数两个参数分别是 resolve 和 reject。它们是两个函数,由 JavaScript引擎提供,不用自己部署。
resolve
resolve 函数的作用是将 Promise 对象的状态从 pending 变为 resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。一旦调用了 resolve 就意味着会执行 .then()。
调用 resolve() 会触发一个异步操作。传递给 .then 与 .catch 的函数会异步地被执行,并且它们也被添加到了任务队列(先进队列再执行)。
resolve 三种类型的参数:
- 普通值或对象
- Promise:当前 Promise 的状态由传入的 Promise 状态决定,相当于状态移交
- 实现 thenable 的对象,会执行该对象的
then方法,且由该then方法决定后续状态
const promise = new Promise((resolve,reject) => {
resolve('123')
})
new Promise((resolve,reject) => {
resolve('resolve message')
resolve({name:'lxx'})
resolve(promise)
const obj = {
then:(resolve,reject)=> {
resolve('resolve message')
}
}
resolve(obj)
}).then(res=> {
console.log('res:',res)
},err => {
console.log('err:',err)
})
reject
reject 函数的作用是将 Promise 对象的状态从 pending 变为 rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。一旦调用 reject 就意味着会执行 .catch
Promise.prototype.then()
Promise 实例具有 then 方法,即 then 方法定义在 Promise.prototype 上。then 方法可以接受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。它们都是可选的,不一定要提供。这两个函数都接受 Promise 对象传出的值作为参数。
采用链式的 then ,可以指定一组按次序调用的回调函数。这时前一个回调函数,有可能返回的还是一个 Promise 对象(即有异步操作)这时后一个回调函数,就会等待该 Promise 对象的状态发生变化才会被调用。
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL)
}).then(function(comments) {
console.log('resolved:', comments)
}, function(err) {
console.log('rejected:', err)
})
上面代码中,第一个 then 方法指定的回调函数,返回的是另一个 Promise 对象。这时第二个 then 方法指定的回调函数,就会等待这个新的 Promise 对象状态发生变化。如果变为 resolved 就会调用第一个回调函数,如果状态变为 rejected,就调用第二个回调函数。
then 方法的返回值
-
返回普通值:普通值会被作为一个新的 Promise 的 resolve 值
promise.then(res => { return 'hello world' }).then(res => { console.log('res2:',res) // res2:hello world }) // 相当于 new 了一个 Promise,后面的 then 方法是最新 Promise 的 then 方法 promise.then(res => { return new Promise((resolve,reject) => { resolve('hello world') }) }).then(res => { console.log('res2:', res) }) -
返回 Promise:第一个 promise 的状态由返回的新的 promise 的结果决定,并且新的 promise 的
resolve()结果会进入下一个then回调。promise.then(res => { return new Promise((resolve,reject)=> { setTimeout(()=> { resolve(123) },3000) }) }).then(res => { console.log('res:', res) }) -
返回实现了 thenable 的对象:promise 的结果由对象中的 then 方法决定
promise.then(res => { return { then:function(resolve, reject) { resolve('hello') } } }).then(res => { console.log('res:', res) })
Promise.prototype.catch()
catch 方法是 .then(null, rejection) 或 .then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。
如果 executor 抛出错误,状态会变为 rejected,就会调用 catch 方法指定的回调函数,处理这个错误。
const p = new Promise((resolve, reject) => {
// executor 抛出错误
throw new Error('promise中的错误')
})
p.then(value => {
console.log(value)
}).catch(reason => {
console.log('reason:', reason)
})
优先捕获第一个 Promise 的异常,如果 then 方法中有 reject,then 方法指定的回调函数,如果运行中抛出错误,也会被 catch 方法捕获。
const p = new Promise((resolve,reject) => {
resolve('fulfilled status')
// 如果executor中抛出错误,会被下面的catch优先捕获
// throw new Error('Promise中的错误')
})
p.then(value => {
throw new Error('then 方法中抛出的错误')
console.log(value)
}).catch(reason => {
console.log('reason:', reason) // reason:Error:then方法中抛出的错误
})
catch 方法的返回值
如果 catch 中返回一个普通值,和 then 中返回普通值一样,会被包裹一个新 Promise。该返回值会作为新 Promise resolve 的 value 进入后续 then 回调中。
const promise = new Promise((resolve,reject)=> {
reject('第一个promise rejected status')
})
promise.then(res=> {
console.log('res:', res)
}).catch(err => {
return 'catch的返回值'
}).then(res => {
console.log('res:', res)
}).catch(err => {
console.log('err:', err)
})
// res: catch的返回值res: catch的返回值
成功还是失败?
异步加载图片的例子
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image()
image.onload = function() {
resolve(image)
}
image.onerror = function() {
reject(new Error('Could not load at' + url))
}
image.src = url
})
}
loadImageAsync('./a.jpg').then(()=> {
console.log('图片加载成功')
}).catch(err => {
console.log(err)
})
上面代码中,使用 Promise 包装了一个图片加载的异步操作。如果加载成功,就调用 resolve 方法,否则就调用 reject 方法。
因为根本没有 './a.jpg' 这个文件,所以会执行 onerror 监听的事件处理程序。而此事件处理程序通过 reject 抛出一个错误,该错误会被 .catch 回调函数捕获,从而输出。
如果调用 resolve 函数和 reject 函数时带有参数,那么它们的参数会传递给回调函数。reject 函数的参数通常是 Error 对象的实例,表示抛出的错误。
Promise 术语回顾
三种状态
- 创建 Promise 时,它既不是成功也不是失败状态,这个状态叫做 pending(待定)
- 当 Promise 返回时,称之为 resolved(已解决)。
一个成功 resolved 的 Promise 称为 fullfilled(兑现)。它返回一个值,可以通过将.then()块链接到 Promise 链的末尾来访问该值。
一个不成功 resolved 的 Promise 被称为 rejected(拒绝)。它返回一个原因(reason),一条错误消息,说明为什么拒绝 Promise。可以通过将.catch块链接到 Promise 链的末尾来访问此原因。
Promise.resolve()
Promise.resolve() 创建了一个决议到传入值的 promise。允许调用时不带参数,直接返回一个 resolved 状态的 Promise 对象。如果希望得到一个 Promise 对象,比较方便的方法就是直接调用 Promise.resolve() 方法。
// Promise.resolve()
var p1 = Promise.resolve(42)
// 手动方式
var p2 = new Promise(function(resolve) {
resolve(42)
})
Promise.reject()
Promise.reject() 创建一个立即被拒绝的 promise,和与它对应的 Promise() 构造器一样。
// Promise.reject()
var p1 = Promise.reject('Oops')
// 手动方式
var p2 = new Promise(function(resolve,reject) {
reject('Oops')
})
执行顺序
executor 会立即执行
let promise = new Promise(function(resolve,reject) {
console.log('Promise')
resolve()
})
// 进入微任务队列
promise.then(function() {
console.log('resolved')
})
console.log('Hi!')
// Promise
// Hi!
// resolved
上面代码中,Promise 新建后立即执行,所以先输出的是 Promise。然后 then 方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以 resolved 最后输出。
下面代码中,调用 resolve(1) 以后,后面的 console.log(2) 还是会执行,并且会首先打印出来。
因为立即 resolved 的 Promise 是在本轮时间循环的末尾执行,总是晚于本轮循环的同步任务。
new Promise((resolve,reject)=> {
resolve(1)
console.log(2)
}).then(res => {
console.log(res)
})
// 2
// 1
new Promise() 小括号中的代码会同步执行;.then() 中回调函数会加入微任务队列等待执行;async 相当于 new Promise(),await 相当于 .then()
立即 resolve() 的 Promise 对象,是在本轮事件循环的结束时执行。
setTimeout(function() {
console.log('three')
},0)
Promise.resolve().then(function (){
console.log('two')
})
console.log('one')
// one
// two
// three
console.log('one') 立即执行,最先输出,.then() 在本轮事件循环结束时执行,setTimeout(fn,0) 在下一轮事件循坏开始时执行。
Promise.all()
它返回一个 promise ,如果所有的值都完成,这个 promise 的结果是完成。一旦它们中的某一个被拒绝,那么这个 promise 就立即被拒绝。
promise.all() 的出现是为了解决下面的痛点:如果需要同时进行多个请求,我们需要知道每个请求是否成功了,才能进行下一步操作。
// 请求1:
let isResult1 = false
let isResult2 = false
$ajax({
url:'',
success() {
isResult1 = true // 将标志置为 true
handleResult()
}
})
// 请求2:
$ajax({
url:'',
success() {
isResult2 = true // 将标志置为 true
handleResult()
}
})
function handleResult() {
if(isResult1 && isResult2) {
// 如果两个请求都成功了,doSomething...
}
}
如果所有期约都成功解决,则合成期约的解决值就是包含所有期约解决值的数组。那么数组中的结果将作为参数传递给 .then() 块中的执行器函数。
Promise.all([
new Promise((resolve,reject)=> {
// 异步操作一
}),
new Promise((resolve,reject)=> {
// 异步操作二
})
]).then(res => {
console.log(res)
})
// 输出的是两次异步操作的结果组成的数组
如果作为参数的 Promise 实例,自己定义了 catch 方法,那么它一旦被 rejected 会触发自身的 catch 方法并不会触发 Promise.all() 的 catch 方法。
const p1 = new Promise((resolve,reject)=> {
resolve('hello')
}).then(res => res).catch(e => e)
const p2 = new Promise((resolve,reject)=> {
throw new Error('报错了')
}).then(res => res).catch(e => e + '11111')
Promise.all([p1, p2])
.then(res => console.log(res))
.catch(e => console.log(e))
// ['hello', Error:报错了11111]
上面代码中,p1 会 resolved,p2 首先会 rejected,但是 p2 有自己的 catch 方法。该方法返回的是一个新的 Promise 实例,p2 指向的实际是这个实例。该实例执行完 catch 方法后,也会变成 resolved,导致 Promise.all() 方法里面的两个实例都会 resolved,因此会调用 then 方法指定的回调函数,而不会调用 catch 方法指定的回调函数。
Promise.allSettled()
等待所有 Promise 都有结果后,统一全部输出 Promise 的状态,无论是 fulfilled 还是 rejected。
Promise.allSettled([p1,p2,p3,'ssss']).then(res => {
console.log(res)
})
// 所有Promise的结果
[
{ status:'fulfilled', value:1111 },
{ status:'rejected', value:2222 },
{ status:'fulfilled', value:3333 },
{ status:'fulfilled', value:'ssss' },
]
Promise.any()
多个 Promise,只要有一个 Promise 发生 resolve,那么就会进入 Promise.any() 的 then 回调。
Promise.race()
Promise.race() 等待第一个 Promise 完成或者拒绝。race本意是竞争。
var p1 = Promise.resolve(42)
var p2 = new Promise(function (resolve) {
setTimeout(function () {
resolve(43)
},100)
})
var v3 = 44
var p4 = new Promise(function (resolve, reject) {
setTimeout(function() {
reject('Oops')
}, 10)
})
Promise.race([p2, p1, v3]).then(function fulfilled(val) {
console.log(val) // 42
})
Promise.race([p2, p4]).then(function fulfilled(val) {
// 不会到达这里
console.log(val)
},function rejected(reason) {
console.log(reason) // Oops
})