JavaScript 中的 Promise 简介
Promise 允许我们执行异步操作。APromise
是一个代理,或者说是一个占位符,用于表示在声明 Promise 时不一定知道的值。我们不会立即获得最终值,而是承诺最终值一定会存在。
当我们需要执行诸如从数据库存储或检索数据或从 API 获取数据之类的操作时,它们很有用。
如何创建承诺
要创建一个承诺,我们只需要创建一个对象的新实例,并将一个函数作为参数与resolve
和reject
参数一起传递。
const promise = new Promise((resolve, reject) => /* do things */)
resolve
如果异步操作成功完成,将会被调用;reject
如果异步操作失败,也会被调用。Promise 可以有三种不同的状态:
pending
是它的初始状态,这意味着它尚未完成fulfilled
表示操作已解决或成功完成rejected
表示操作失败
因此,当 Promise 首次创建时,其状态为pending
。然后,一旦异步操作发生,如果成功解析,其状态将变为fulfilled
,并且它将调用函数resolve
。否则,它将变为 ,rejected
并且它将调用函数reject
。
因此,承诺的一个简单示例可能如下所示:
const promise = new Promise((resolve, reject) => {
console.log('Asynchronous operation started')
setTimeout(() => Math.random() > 0.15
? resolve('Success!')
: reject('Oops, something went wrong!')
, Math.random() * 700 + 800)
})
我们首先会在控制台中收到一条消息,告知我们操作已开始。然后,在 0.8 到 1.5 秒后,该 Promise 将会解析(概率约为 85%)并返回成功消息,或者失败(概率约为 15%)并返回失败消息。
then
或者catch
当承诺解决时会发生什么
通常,在异步操作解析之后,我们会想要对返回的数据进行一些处理。例如,如果我们从数据库中检索信息,我们可能想要实际使用该信息。这时方法then
和catch
就派上用场了。
then
该方法then
接受两个可选参数,onFulfilled
和onRejected
。如果promise为,则调用第一个参数fulfilled
;如果promise为,则调用第二个参数rejected
。这两个函数都会收到一个参数,即promise返回的值。
基于我们之前的承诺,它看起来可能像这样:
promise.then(data => {
writeMsg(data) // Writes 'Success!'
launchFireworks() // Launches fireworks
}, rejection => {
writeMsg(rejection) // Writes 'Oops, something went wrong!'
playDefeatMusic() // Plays sad, defeat music
})
不过,通常情况下,你只想传递onFulfilled
参数,而将处理拒绝的逻辑留给catch
方法处理。所以,我们可以这样写:
promise.then(data => {
writeMsg(data)
launchFireworks()
})
如果您只需要将一个函数传递给then
,则只需传递其名称,然后then
就会负责调用它并将承诺的结果作为函数的参数传递。
//Both these thens do the same
promise.then(data => doStuff(data))
promise.then(doStuff)
catch
该方法catch
接受参数onRejected
,如果 Promise 拒绝,则会调用该方法。除此之外,其工作原理与 完全相同then
。
promise
.then(data => {
writeMsg(data)
launchFireworks()
})
.catch(error => {
writeMsg(error)
playDefeatMusic()
})
就像一样then
,你可以在调用它时使用简写:
promise
.then(doStuff)
.catch(logError)
链接then
和catch
then
和返回的内容catch
也将被包装在 Promise 中。因此,即使它们实际上并非执行异步操作,也可以将它们串联起来。
promise
.then(transformData)
.then(doMoreAsyncStuff)
.then(transformData)
.catch(dealWithError)
但也许我们该看一个真实的例子,而不是一堆模拟函数。假设我们使用 MongoDB 来存储锻炼数据。有时,我们需要检索这些数据。我们可以这样做:
const mongoDB = require('mongodb')
mongoDB.MongoClient.connect(URI)
.then(client => client.db('exercise'))
.then(db => db.collection('workouts').find(query))
.then(data => data.toArray())
.then(console.log)
.catch(console.warn)
这会创建与 MongoClient 的连接,它本身已经返回了一个 Promise。然后,它会选择数据库exercise
。接着,它会选择集合workouts
并查找符合指定条件的内容query
。最后,它会将返回的数据转换为数组。如果一切顺利,它会将数据记录到控制台中。如果在此过程中出现任何问题,它会将其作为警告记录在控制台中。
创建一个返回承诺的函数
如果我们使用 MongoDBfetch
或任何返回 Promise 的函数,我们只需将方法链式调用then
到catch
其中即可,这就是使用 Promise 所需要做的全部工作。但情况并非总是如此。有时,我们可能需要先创建一个返回 Promise 的函数。
例如,假设我们决定使用某个数据库作为练习数据库,该数据库的 JavaScript API 不返回 Promise。相反,它需要回调来处理返回的数据。因此,DbHandler.find(query, callback)
当我们想要处理检索到的信息时,我们必须执行类似的操作。假设回调应该接受两个参数data
和error
,分别是检索到的数据和可能发生的错误。
然后我们可以创建一个函数来查找数据库中的内容并将其作为承诺返回:
const findPromise = query => new Promise((resolve, reject) => {
DbHandler.find(query, (data, error) => {
if (error == null) return resolve(data)
else return reject(error)
}
})
现在,当我们想要在数据库中查找内容时,我们可以像调用任何其他返回承诺的函数一样调用我们精心设计的函数:
findPromise(query)
.then(doStuff)
.catch(console.warn)