一、Promise 概况
(一)设计初衷
在 JavaScript 中,Promise 主要用于解决异步编程中回调地狱带来的一系列问题,同时让异步代码的逻辑更加清晰、可维护和易于扩展。
解决回调地狱问题
回调地狱是指在处理多个异步操作时,由于嵌套使用回调函数,导致代码结构变得复杂、难以理解和维护。而 Promise 通过链式调用的方式,避免了多层嵌套回调函数的问题,让代码结构更加清晰。
const fs = require('fs');
fs.readFile('file1.txt', 'utf8', function (err, data1) {
if (err) {
console.error(err);
} else {
fs.readFile('file2.txt', 'utf8', function (err, data2) {
if (err) {
console.error(err);
} else {
fs.readFile('file3.txt', 'utf8', function (err, data3) {
if (err) {
console.error(err);
} else {
console.log(data1, data2, data3);
}
});
}
});
}
});
使用 Promise 可以将上述代码改写成链式调用的形式,使代码更易读:
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
readFile('file1.txt', 'utf8')
.then(data1 => {
return readFile('file2.txt', 'utf8');
})
.then(data2 => {
return readFile('file3.txt', 'utf8');
})
.then(data3 => {
console.log(data1, data2, data3);
})
.catch(err => {
console.error(err);
});
统一错误处理
在回调地狱中,每个回调函数都需要单独处理错误,导致错误处理代码分散,增加了代码的复杂度。而 Promise 提供了统一的错误处理机制,通过 .catch() 方法依赖错误的传递,可以捕获整个 Promise 链中的错误,使错误处理更加集中和方便。
更好的代码复用
Promise 可以将异步操作封装成独立的函数,返回一个 Promise 对象,这样就可以在不同的地方复用这些异步操作。例如,将读取文件的操作封装成一个返回 Promise 的函数:
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);
function readFilePromise(filePath) {
return readFile(filePath, 'utf8');
}
readFilePromise('file1.txt')
.then(data => {
console.log(data);
})
.catch(err => {
console.error(err);
});
更清晰的异步操作状态管理
Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败),并且状态一旦改变就不会再变。这种状态管理机制让开发者可以更清晰地了解异步操作的执行情况,便于进行后续的处理。例如,在异步操作进行中可以显示加载状态,操作成功后显示结果,操作失败后显示错误信息。
(二)设计结构
二、Promise 细节
(一)Promise 对象
Promise 对象核心构成:
status:记录Promise状态,值可以为:pending、fulfilled、rejeced三选一。value:记录当Promise状态转为fulfilled时,获取的值。由调用者promise使用resovle传进来reason:记录当Promise状态为rejected时,获取的值。由调用者promise使用reject传进来resovle和reject:一对函数(类似开关遥控器,控制 Promise 的状态变化,并可以传递成功/失败值至回调函数),是控制pending的promise状态是转向fulfilled还是rejected。用法:resovle(value)、reject(reason)then:注册回调函数x,创建一个新的promise用于包装回调函数x的返回结果,从而实现链式调用。
回调函数
x必须是异步执行,即使当前 x 符合执行条件,也需要异步执行。类似于放到setTimeout内,如 setTimeout(x,0),本次主循环不执行。
(二)Promise 链式调用
Promise 的 then 方法可以返回一个新的包装 Promise。注意,每调用一次 then 方法做了两件事:
- 将
then方法参数的成功和失败回调函数注册,待Promise状态改变时集中调用 - 新创建一个包装
Promise,实现链式调用。包装Promise的状态根据成功/失败回调函数的返回值x来确定。存在5种依赖情况:- 若函数返回值
x不是对象、函数,则直接将x兑现包装promise - 若函数返回值
x是一个promise对象,则根据x的fulfilled、rejected状态确定包装promise的状态, - 若函数返回值
x是一个对象或函数,但x没有属性then,则包装promise的状态为rejected - 若函数返回值
x是一个对象或函数,x有属性then,但then不是函数,则用x兑现包装promise - 若函数返回值
x是一个对象或函数,x有属性then,且then是函数,则调用then函数状态确定包装promise状态
- 若函数返回值
以下代码为例
const promise = new Promise((resolve, reject) => {
resolve('Promise1 成功');
}).then(function fn1(value){return new Promise((resolve,reject)=>{reject("Promise2 失败")})})
.then(function fn2(value){
console.log('成功:',value);
},function fn3(reason){
console.log('失败:',reason);//控制台输出“失败: Promise2 失败 ”
})
我理解的链式调用思路如下:
(三)异步和同步
Promise 异步、同步程序块执行的原则:
- 同步调用传入
Promise构造器的参数方法 - 异步调用
Promise使用then注册的回调函数 - 在链式调用中,下一个
then注册的回调函数较上一个then注册的回调函数异步执行
console.log("00")
const promise = new Promise((resolve, reject) => {
console.log("11");//与主程序同步调用
resolve('Promise1 成功');
}).then(
function fn1(value){
console.log("33");//第一个then内的注册回调函数,相比Promise构造器内传入的方法为异步调用
return new Promise((resolve,reject)=>{reject("Promise2 失败")})
}).then(
function fn2(value){
console.log('成功:',value);
},
function fn3(reason){
console.log("44");//第二个then内的注册回调函数,相比第一个then内注册回调函数为异步调用
console.log('失败:',reason);
}
)
console.log("22");
以上代码按照 00、11、22、33、44 顺序输出。
三、结束语
本文主要聚焦我认为最需要了解的Promise链式调用工作逻辑,解释链式调用后一共产生了多少个Promise对象,各Promise对象之间的关系是怎么样的,Promise如何进行状态传递和值传递。了解这些对学习Promise的各种方法使用、async await知识都会有所帮助。希望本文对你在学习Promise知识中有所启发。