Promise是JavaScript中处理异步代码的一种方式,不需要在代码中编写过多的回调函数。
- 引言
- Promise的工作原理简述
- [哪些JS API使用Promise?](#哪些JS API使用Promise?)
- 创建Promise
- 消费Promise
- 链接Promise
- 处理错误
- 编排Promise
- 常见错误
- [未捕获 TypeError: undefined 不是一个Promise](#未捕获TypeError: undefined 不是一个Promise)
引言
Promise通常被定义为"最终可用值的代理"。
Promise是处理异步代码的一种方式,不需要在代码中编写过多的回调函数。
尽管Promise已经存在多年,但它们在ES2015进行了标准化和引入,然后在ES2017中被异步函数取代。
异步函数使用Promise API作为基础,因此理解Promise是基本的,即使在较新的代码中,您可能会使用异步函数而不是Promise。
Promise的工作原理简述
一旦调用了Promise,它将以"挂起"状态开始,这意味着调用者函数将继续执行,同时等待Promise执行自己的处理工作,并向调用者函数提供一些反馈。
此时,调用者函数等待它返回一个"已解决"或"已拒绝"状态的Promise,但函数会在Promise执行其工作时继续执行。
哪些JS API使用Promise?
除了您自己的代码和库代码之外,Promise还被现代Web API使用,例如:
- Battery API
- Fetch API
- Service Workers
在现代JavaScript中,您可能不会遇到不使用Promise的情况,因此让我们直接深入了解它们。
创建Promise
Promise API公开了一个Promise构造函数,您可以使用new Promise()
进行初始化:
let done = true
const isItDoneYet = new Promise((resolve, reject) => {
if (done) {
const workDone = 'Here is the thing I built'
resolve(workDone)
} else {
const why = 'Still working on something else'
reject(why)
}
})
如您所见,Promise检查全局变量done
,如果为true
,则返回一个已解决的Promise,否则返回一个被拒绝的Promise。
使用resolve
和reject
,可以向后通信一个值,在上述案例中,我们只返回一个字符串,但也可以是一个对象。
消费Promise
在上一节中,我们介绍了如何创建Promise。
现在让我们看看Promise如何被"消费"或使用。
const isItDoneYet = new Promise()
//...
const checkIfItsDone = () => {
isItDoneYet
.then(ok => {
console.log(ok)
})
.catch(err => {
console.error(err)
})
}
运行checkIfItsDone()
将执行isItDoneYet()
Promise,并等待其解决,使用then
回调函数,如果有错误,则在catch
回调函数中处理。
链接Promise
可以将一个Promise返回给另一个Promise,创建Promise链。
链式Promise的一个很好的示例是Fetch API,它是XMLHttpRequest API的一种封装层,我们可以使用它来获取资源并在资源获取时排队一系列Promise来执行。
Fetch API是一个基于Promise的机制,调用fetch()
等效于使用new Promise()
定义自己的Promise。
链接Promise的实例
const status = response => {
if (response.status >= 200 && response.status < 300) {
return Promise.resolve(response)
}
return Promise.reject(new Error(response.statusText))
}
const json = response => response.json()
fetch('/todos.json')
.then(status)
.then(json)
.then(data => {
console.log('请求成功,JSON响应为', data)
})
.catch(error => {
console.log('请求失败', error)
})
在这个示例中,我们调用fetch()
来从根目录中的todos.json
文件中获取一个TODO项列表,然后我们创建了一个Promise链。
运行fetch()
返回一个response,它有许多属性,在这些属性中,我们引用了:
status
,表示HTTP状态码的数字值statusText
,表示状态消息,如果请求成功,则为OK
response
还有一个json()
方法,返回一个Promise,该Promise将以处理和转换为JSON的内容解析。
因此,根据这些前提条件,发生以下情况:链中的第一个Promise是一个我们定义的函数status()
,它检查响应状态,如果不是成功响应(介于200和299之间),它拒绝该Promise。
这个操作将导致Promise链跳过所有列出的链接Promise,并直接跳转到底部的catch()
语句,记录Request failed
文本以及错误消息。
如果成功,它调用我们定义的json()
函数。由于前一个Promise成功时返回了response
对象,我们将其作为第二个Promise的输入。
在这种情况下,我们返回JSON处理后的数据,因此第三个Promise直接接收到JSON:
.then((data) => {
console.log('请求成功,JSON响应为', data)
})
我们将其记录到控制台中。
处理错误
在前一节中的示例中,我们在Promise链中添加了一个附加的catch
。
链中的任何Promise失败并引发错误或拒绝Promise时,控制权会转到链中最近的catch()
语句。
new Promise((resolve, reject) => {
throw new Error('错误')
}).catch(err => {
console.error(err)
})
// 或者
new Promise((resolve, reject) => {
reject('错误')
}).catch(err => {
console.error(err)
})
级联错误
如果在catch()
中引发错误,则可以追加第二个catch()
来处理它,依此类推。
new Promise((resolve, reject) => {
throw new Error('错误')
})
.catch(err => {
throw new Error('错误')
})
.catch(err => {
console.error(err)
})
编排Promise
Promise.all()
如果需要同步不同的Promise,Promise.all()
可以帮助您定义一个Promise列表,并在它们全部完成时执行某些操作。
示例:
const f1 = fetch('/something.json')
const f2 = fetch('/something2.json')
Promise.all([f1, f2])
.then(res => {
console.log('结果的数组', res)
})
.catch(err => {
console.error(err)
})
ES2015解构赋值语法还允许您执行以下操作:
Promise.all([f1, f2]).then(([res1, res2]) => {
console.log('结果', res1, res2)
})
您不限于使用fetch
,任何Promise都可以正常运行。
Promise.race()
Promise.race()
只要传递给它的Promise中有一个解决,就开始运行,并且使用第一个解决的结果运行附加的回调一次。
示例:
const promiseOne = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one')
})
const promiseTwo = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two')
})
Promise.race([promiseOne, promiseTwo]).then(result => {
console.log(result) // 'two'
})
常见错误
未捕获 TypeError: undefined 不是一个Promise
如果控制台中出现Uncaught TypeError: undefined is not a promise
错误,请确保使用new Promise()
而不是Promise()