Promise 是 JavaScript 中用于异步编程的一个重要概念。它表示一个尚未完成但预期将来会完成的操作的结果。使用 Promise 可以优雅地处理异步操作及其结果,避免回调地狱(callback hell)的出现。下面我将从基本概念、创建和使用 Promise、以及常见的方法等方面进行解析。
创建一个 Promise 对象非常简单,只需要使用 new Promise()
构造函数并传入一个执行器函数(executor function),这个函数接受两个参数:resolve
和 reject
。
setTimeout(() => { // 第一层回调函数
console.log('延迟1秒后输出')
setTimeout(() => { // 第二层回调函数
console.log('延迟2秒后输出')
setTimeout(() => { // 第三层回调函数
console.log('延迟3秒后输出')
}, 3000)
}, 2000)
}, 1000)
回调地狱的缺点:
为了解决回调地狱的问题,ES6中新增了Promise概念。
2.Promise
Promise是一个构造函数
const p = new Promise()
new
出来的Promise
实例对象,代表一个异步操作
查看Promise实例
console.dir(promise)
可以看到prototype上有then()方法
Promise的.then()方法
Promise.prototype上挂载了.then()方法,每一次new Promise()得到的实例对象,都可以通过原型链的方式访问到.then()方法 如 p.then()。
.then(成功的回调函数,失败的回调函数)
p.then(result=>{},err=>{})
预先
指定成功
和失败
的回调函数(1)基于回调函数读取文件
import fs from 'fs'
fs.readFile('./file/01.txt', 'utf8', (err1, res1) => {
if (err1) return console.log(res1.message)
console.log(res1) // 123 读取文件1成功
fs.readFile('./file/02.txt', 'utf8', (err2, res2) => {
if (err2) return console.log(err2.message)
console.log(res2) // 456 读取文件2成功
fs.readFile('./file/03.txt', 'utf8', (err3, res3) => {
if (err3) return console.log(err3.message)
console.log(res3) // 789 读取文件3成功
})
})
})
(2)基于Promise异步读取文件
由于node.js官方提供的fs模块仅支持以回调函数的方式读取文件,不支持pormise调用方式。因此,
需要先安装then-fs
第三方包,从而支持我们基于Promise方式读取文件内容
import thenFs from 'then-fs'
thenFs.readFile('./file/01.txt', 'utf8').then(res1 => { console.log(res1) })
thenFs.readFile('./file/02.txt', 'utf8').then(res2 => { console.log(res2) })
thenFs.readFile('./file/03.txt', 'utf8').then(res3 => { console.log(res3) })
调用then-fs提供的readFile()方法,可以异步读取文件内容,thenFs.readFile()的返回值是Promise实例对象
因此可以调用.then()为每个Promise异步操作指定成功和失败的回调函数
由于是异步操作,得到的顺序不一
(3)基于Promise的链式调用
import thenFs from "then-fs"
thenFs.readFile('./file/10.txt', 'utf8').catch(err => {
console.log(err.message)
}).then(r1 => {
console.log(r1)
return thenFs.readFile('./file/02.txt', 'utf8')
}).then(r2 => {
console.log(r2)
return thenFs.readFile('./file/03.txt', 'utf8')
}).then(r3 => {
console.log(r3)
})
在第一个.then()中返回了一个新的Promise实例对象,继续调用.then(),为上一个.then的返回值(新的Promise实例),指定成功之后的回调函数
Promise的链式操作中如果发生了错误,可以使用Promise.prototype.catch方法进行捕获处理
如果不希望前面的错误导致后续的.then无法正常执行,则可以将.catch的调用提前(.catch放在最后的话前面产生的错误会导致后面.then无法被执行)
.then()方法的特性:
如果上一个.then()方法中返回了一个新的Promise实例对象,则可以通过下一个.then()继续进行处理。
通过.then()方法的链式调用,解决回调地狱问题
(4)Promise.all()
Promise.all()方法会发起并行的Promise异步操作,等所有的异步操作全部结束后,才会执行下一步的.then操作 (等待机制)
import thenFs from "then-fs"
const promiseArr = [
// 三个异步读文件的promise实例
thenFs.readFile('./file/01.txt', 'utf8'),
thenFs.readFile('./file/02.txt', 'utf8'),
thenFs.readFile('./file/03.txt', 'utf8')
]
Promise.all(promiseArr).then(res => { // promise实例的顺序就是最终拿到的结果的顺序
console.log(res) // ['123','456','789']
}).catch(err => {
console.log(err.message)
})
(5)Promise.race()
Promise.race()
方法会发起并行的Promise异步操作,只要任何一个异步操作完成,就会立即执行下一步的.then操作 (赛跑机制
)。
import thenFs from "then-fs"
const promiseArr = [
// 三个异步读文件的promise实例
thenFs.readFile('./file/01.txt', 'utf8'),
thenFs.readFile('./file/02.txt', 'utf8'),
thenFs.readFile('./file/03.txt', 'utf8')
]
Promise.race(promiseArr).then(res => { // 只要任何一个异步操作完成,就立即执行成功的回调函数
console.log(res)
}).catch(err => {
console.log(err.message)
})
方法接收一个形参 fpath,代表要读取文件的路径
方法的返回值为Promise实例对象
new Promise() 只是创建了一个形式上的异步操作
如果想要创建具体的异步操作,则需要在new Promise()构造函数期间,传递一个function()函数, 将具体的异步操作定义到function函数内部
3.async/await
async/await是ES8引入的新语法,用来简化Promise异步操作。在async/await出现之前,我们只能通过链式.then()方式处理promise异步操作。
链式.then()调用的优点:
解决了回调地狱的问题
链式.then()调用的缺点:
代码冗余,阅读性差,不易理解
(1)使用async/await简化Promise异步操作
import thenFs from "then-fs"
async function getFile() {
const r1 = await thenFs.readFile('./file/01.txt', 'utf8')
console.log(r1)
const r2 = await thenFs.readFile('./file/02.txt', 'utf8')
console.log(r2)
const r3 = await thenFs.readFile('./file/03.txt', 'utf8')
console.log(r3)
}
getFile()
如果方法内部用到了await,这个方法就必须被async修饰
加了await后,thenFs.readFile()返回的就不再是promise实例,而是得到了异步操作后具体的内容
有了async和await就不需要.then()来获得最后的结果了
(2)async和await的注意事项
import thenFs from "then-fs"
console.log('A')
async function getFile() {
console.log('B')
const r1 = await thenFs.readFile('./file/01.txt', 'utf8')
const r2 = await thenFs.readFile('./file/02.txt', 'utf8')
const r3 = await thenFs.readFile('./file/03.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
console.log('D')
}
getFile() // A B C 123 456 789 D
console.log('C')