Appearance
Promise
Promise,保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
Promise 是一个对象,可以获取异步操作的消息。
- 对象状态不受外界影响,只有异步操作可以决定当前状态
pending:进行中fulfilled:已成功rejected:已失败
- 状态一旦改变,就不会再变
- 两种状态变化:从
pending变为fulfilled,从pending变为rejected - 实时获取:状态改变后,添加回调函数,会立即得到结果。而事件是错过了再去监听,无法得到结果
- 两种状态变化:从
优点
- 异步操作以同步操作流程表达,避免回调地狱
- Promise 对象提供统一接口,控制异步操作更容易
缺点
- 一旦执行,无法中途取消
Promise - 不设回调,
Promise内部错误,不会反应到外部 - 处于
pending状态,无法得知目前到哪一阶段(刚刚开始 或 即将完成)
基本用法
ES6 Promise对象是一个构造函数,用来生成Promise实例
Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject(参数均为可选)。
resolve:在异步操作成功时调用,并将异步操作的结果作为参数传递出去。状态从 pending 到 fulfilledreject在异步操作失败时调用,并将异步操作的错误作为参数传递出去。状态从 pending 到 rejected
Promise实例,用then方法两个参数分别指定resolved状态和rejected状态的回调函数
js
promise.then(
function (value) {
// success
},
function (error) {
// failure
}
)Promise 新建后立即执行
js
let promise = new Promise(function (resolve, reject) {
console.log('Promise')
resolve()
})
promise.then(function () {
console.log('resolved.')
})
console.log('Hi!')
// Promise
// Hi!
// resolved代码执行:p2 返回 p1 ,p2 返回一个 Promise,导致 p2 状态无效,依赖于 p1 状态。
1 秒后返回 p1,3 秒后,p1 变为 rejected,出发 catch
js
const p1 = new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
setTimeout(() => resolve(p1), 1000)
})
p2.then((result) => console.log(result)).catch((error) => console.log(error))状态传递:p1状态会传递给p2
- 如果
p1是pending,p2回调等待p1状态改变 - 如果
p1状态时resolved或rejected,p2回调立刻执行
resolve或reject不会终结 promise 参数执行
js
new Promise((resolve, reject) => {
resolve(1)
console.log(2)
}).then((r) => {
console.log(r)
})
// 2
// 1执行时机:resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务
Promise 原型方法
Promise.prototype.then
Promise.prototype.then():定义在对象原型上,为Promise实例添加状态改变时回调函数
返回新的Promise实例,可以链式运算
js
getJSON('/posts.json')
.then(function (json) {
return json.post
})
.then(function (post) {
// ...
})链式调用时,等待前一个 Promise 状态变化
Promise.prototype.catch
Promise.prototype.catch():指定发生错误时的回调函数。是.then(null, rejection)或.then(undefined, rejection)的别名。
js
.then(null, rejection)
.then(undefined, rejection)
// catch是上述两种写法的别名
Promise.prototype.catch()注意事项
- 不要在
then()中定义Reject状态回调函数,应使用catch(),因为更接近同步(try catch)写法 promise抛出一个错误,就被catch()方法指定的回调函数捕获。以下三种写法等效
js
// 写法一
const promise = new Promise(function (resolve, reject) {
throw new Error('test')
})
// 写法二
const promise = new Promise(function (resolve, reject) {
try {
throw new Error('test')
} catch (e) {
reject(e)
}
})
// 写法三
const promise = new Promise(function (resolve, reject) {
reject(new Error('test'))
})
// 捕获错误
promise.catch(function (error) {
console.log(error)
})异步操作抛出错误,状态就会变为
rejected,就会调用catch()处理这个错误then()回调函数,如果运行中抛出错误,也被catch()方法捕获状态改变后,抛出错误无效
js
const promise = new Promise(function (resolve, reject) {
resolve('ok')
throw new Error('test')
})
promise
.then(function (value) {
console.log(value)
})
.catch(function (error) {
console.log(error)
})
// ok- Promise 对象的错误会一直向后传递,直到被捕获为止(错误总是会被下一个
catch语句捕获)
js
getJSON('/post/1.json')
.then(function (post) {
return getJSON(post.commentURL)
})
.then(function (comments) {
// some code
})
.catch(function (error) {
// 处理前面三个Promise产生的错误
})与 try/catch 代码块区别
如果没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码。不会中断外层代码,“Promise 会吃掉错误”
js
const someAsyncThing = function () {
return new Promise(function (resolve, reject) {
// 下面一行会报错,因为x没有声明
resolve(x + 2)
})
}
someAsyncThing().then(function () {
console.log('everything is great')
})
setTimeout(() => {
console.log(123)
}, 2000)
// Uncaught (in promise) ReferenceError: x is not defined
// 123catch 抛出错误,需要之后的 catch 捕获
js
someAsyncThing()
.then(function () {
return someOtherAsyncThing()
})
.catch(function (error) {
console.log('oh no', error)
// 下面一行会报错,因为y没有声明
y + 2
})
.catch(function (error) {
console.log('carry on', error)
})
// oh no [ReferenceError: x is not defined]
// carry on [ReferenceError: y is not defined]Promise.prototype.finally
ES2018 - Promise.prototype.finally():不管 Promise 对象最后状态如何,都会执行的操作。
- 回调函数不接受参数,意味无法知道状态为 fulfilled 还是 rejected
finally本质上是then方法的特例
js
promise.finally(() => {
// 语句
})
// 等同于
promise.then(
(result) => {
// 语句
return result
},
(error) => {
// 语句
throw error
}
)polyfill
js
Promise.prototype.finally = function (callback) {
let P = this.constructor
return this.then(
(value) => P.resolve(callback()).then(() => value),
(reason) =>
P.resolve(callback()).then(() => {
throw reason
})
)
}finally方法:总是会返回原来的值
js
// resolve 的值是 undefined
Promise.resolve(2).then(
() => {},
() => {}
)
// resolve 的值是 2
Promise.resolve(2).finally(() => {})
// reject 的值是 undefined
Promise.reject(3).then(
() => {},
() => {}
)
// reject 的值是 3
Promise.reject(3).finally(() => {})Promise 方法
Promise.all
Promise.all():将多个 Promise 实例,包装成一个新的 Promise 实例。
js
const p = Promise.all([p1, p2, p3])- 参数可以不是数组,但必须具有 Iterator 接口
- 参数都不是 Promise,会调用 Promise.resolve
- 返回的每个成员都是 Promise 实例
只有p1、p2、p3状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p回调函数
只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
定义了自己的 catch,不会触发 Promise.all 方法 catch
js
const p1 = new Promise((resolve, reject) => {
resolve('hello')
})
.then((result) => result)
.catch((e) => e)
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了')
})
.then((result) => result)
.catch((e) => e)
Promise.all([p1, p2])
.then((result) => console.log(result)) // ["hello", Error: 报错了]
.catch((e) => console.log(e))如果p2没有自己的catch方法,就会调用Promise.all()的catch方法
js
const p1 = new Promise((resolve, reject) => {
resolve('hello')
}).then((result) => result)
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了')
}).then((result) => result)
Promise.all([p1, p2])
.then((result) => console.log(result))
.catch((e) => console.log(e)) // Error: 报错了Promise.race
Promise.race():多个 Promise 实例,包装成一个新的 Promise 实例
js
const p = Promise.race([p1, p2, p3])- 参数都不是 Promise,会调用 Promise.resolve
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。率先改变的 Promise 实例的返回值,就传递给p的回调函数
应用:异步请求超时处理
js
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
])
p.then(console.log).catch(console.error)Promise.allSettled
ES2020 - Promise.allSettled():接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。等到所有参数实例都返回结果,包含 fulfilled 和 rejected 两种情况。
返回新 Promise 实例状态总是 fulfilled,不会为 rejected。状态为 fulfilled 时,会向回调函数中传入参数数组。
js
const resolved = Promise.resolve(42)
const rejected = Promise.reject(-1)
const allSettledPromise = Promise.allSettled([resolved, rejected])
allSettledPromise.then(function (results) {
console.log(results)
})
// [
// { status: 'fulfilled', value: 42 },
// { status: 'rejected', reason: -1 }
// ]返回数组里面 有 rejected 和 fulfilled 状态,但是 Promise 实例状态为 fulfilled
参数数组:每个对象都有status属性,成功为 value,失败为 reason
js
// 异步操作成功时
{status: 'fulfilled', value: value}
// 异步操作失败时
{status: 'rejected', reason: reason}可以通过 status 过滤出 所有的 rejected 和 fulfilled 请求
与 Promise.all 区别
Promise.all()无法确定所有请求都结束。Promise.allSettled(),可以保证所有请求结束,不管结果。
js
const urls = [
/* ... */
]
const requests = urls.map((x) => fetch(x))
try {
await Promise.all(requests)
console.log('所有请求都成功。')
} catch {
console.log('至少一个请求失败,其他请求可能还没结束。')
}Promise.any
ES2021 - Promise.any():接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例返回。
有一个变成
fulfilled状态,包装实例就会变成fulfilled状态所有参数实例都变成
rejected状态,包装实例就会变成rejected状态
与 Promise.race 区别
Promise.any()跟Promise.race()相似,但Promise.any()不会因为某个 Promise 变成rejected状态而结束
Promise.any()抛出的错误是一个 AggregateError 实例,相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误
js
var resolved = Promise.resolve(42)
var rejected = Promise.reject(-1)
var alsoRejected = Promise.reject(Infinity)
Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
console.log(result) // 42
})
Promise.any([rejected, alsoRejected]).catch(function (results) {
console.log(results) // [-1, Infinity]
})Promise.resolve
Promise.resolve():将现有对象转为 Promise 对象
js
Promise.resolve('foo')
// 等价于
new Promise((resolve) => resolve('foo'))参数的四种情况
Promise 实例:将不做任何修改、原封不动地返回这个实例
thenable对象:将这个对象转为 Promise 对象,然后就立即执行thenable对象的then()方法
js
let thenable = {
then: function (resolve, reject) {
resolve(42)
}
}
let p1 = Promise.resolve(thenable)
p1.then(function (value) {
console.log(value) // 42
})
thenable对象:具有then方法的对象
- 参数不是具有
then()方法的对象,或根本就不是对象:返回一个新的 Promise 对象,状态为resolved
js
const p = Promise.resolve('Hello')
p.then(function (s) {
console.log(s)
})
// Hello- 不带有任何参数:直接返回一个
resolved状态的 Promise 对象
js
setTimeout(function () {
console.log('three')
}, 0)
Promise.resolve().then(function () {
console.log('two')
})
console.log('one')
// one
// two
// threesetTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log('one')则是立即执行
Promise.reject
Promise.reject(reason):返回一个新的 Promise 实例,该实例的状态为rejected
js
const p = Promise.reject('出错了')
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
})
// 出错了Promise.reject()方法的参数是一个字符串,后面catch()方法的参数e就是这个字符串
Promise.try
场景:不知道或者不想区分函数f是同步函数还是异步操作,但想用 Promise 来处理。这样不管f是否包含异步操作,都可以then、catch方法处理f抛出的错误。一般采用以下写法
js
Promise.resolve().then(f)问题:f是同步函数,但通过Promise后,变成异步操作,在本轮事件循环末尾执行。
js
const f = () => console.log('now')
Promise.resolve().then(f)
console.log('next')
// next
// now预期:同步代码同步执行,异步代码异步执行,具有统一API
解决:
- async 函数写法
js
const f = () => console.log('now')
(async () => f())()
.then(...)
.catch(...)
console.log('next');
// now
// next
async会吃掉 抛出错误,使用promise.catch方法捕获
- new Promise()
js
const f = () => console.log('now')
;(() => new Promise((resolve) => resolve(f())))()
console.log('next')
// now
// next- Promise.try()
Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。
js
const f = () => console.log('now')
Promise.try(f)
console.log('next')
// now
// next应用
抛出异步错误,通过 catch 捕获
js
database.users.get({id: userId})
.then(...)
.catch(...)抛出同步错误,需要用 try...catch 捕获
js
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}js
try {
database.users.get({id: userId})
.then(...)
.catch(...)
} catch (e) {
// ...
}
Promise.try(() => database.users.get({id: userId}))
.then(...)
.catch(...)Promise.try就是模拟try代码块,就像promise.catch模拟的是catch代码块
ployfill
js
export function promiseTry(func) {
return new Promise(function (resolve, reject) {
resolve(func())
})
}应用
加载图片
js
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 image at ' + url))
}
image.src = url
})
}封装 AJAX
js
const getJSON = function (url) {
const promise = new Promise(function (resolve, reject) {
const handler = function () {
if (this.readyState !== 4) {
return
}
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
const client = new XMLHttpRequest()
client.open('GET', url)
client.onreadystatechange = handler
client.responseType = 'json'
client.setRequestHeader('Accept', 'application/json')
client.send()
})
return promise
}
getJSON('/posts.json').then(
function (json) {
console.log('Contents: ' + json)
},
function (error) {
console.error('出错了', error)
}
)Generator 函数与 Promise 的结合
Generator 函数g之中,有一个异步操作getFoo,它返回的就是一个Promise对象。函数run用来处理这个Promise对象,并调用下一个next方法。
js
function getFoo() {
return new Promise(function (resolve, reject) {
resolve('foo')
})
}
const g = function* () {
try {
const foo = yield getFoo()
console.log(foo)
} catch (e) {
console.log(e)
}
}
function run(generator) {
const it = generator()
function go(result) {
if (result.done) return result.value
return result.value.then(
function (value) {
return go(it.next(value))
},
function (error) {
return go(it.throw(error))
}
)
}
go(it.next())
}
run(g)