Promise是JavaScript中处理异步代码的一种方式,不需要在代码中编写过多的回调函数。

引言

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。

使用resolvereject,可以向后通信一个值,在上述案例中,我们只返回一个字符串,但也可以是一个对象。


消费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()