Promise诞生背景与核心概念

24 阅读6分钟

本专栏聚焦Promise的核心原理与高级应用,包含: ✓ Promise A+规范深度解读 ✓ 手写实现与源码分析 ✓ 异步编程设计模式 ✓ 性能调优与错误处理

适合有JavaScript基础,希望深入异步编程的开发者。我们将用最少的篇幅,讲透最核心的知识。

引言:为什么需要Promise?

在本篇开始之前,先想象一下这样的场景:我们需要从服务器获取用户信息,然后根据用户信息获取订单列表,接着根据订单获取商品详情,最后渲染页面。在早期的JavaScript中,我们可能需要写出这样的代码:

getUserInfo(userId, function(userInfo) {
    getOrderList(userInfo.id, function(orders) {
        orders.forEach(function(order) {
            getProductDetail(order.productId, function(product) {
                renderProduct(product, function() {
                    // 后面可能还有更多回调处理函数
                });
            });
        });
    });
});

这就是臭名昭著的 回调地狱(Callback Hell) 。代码不仅难以阅读和维护,错误处理也变得异常困难。为了解决这个问题,Promise应运而生。

同步与异步

在正式介绍Promise之前,我们先来了解一下JavaScript中的同步与异步的相关概念。

同步编程

同步编程对应内存中顺序执行的处理器指令,其特点是代码按顺序执行,每行代码必须等待上一行执行完成后才能进行下一行代码的执行。

console.log('步骤1');  // 打印 步骤1
let result = doSomething();  // 这里会阻塞线程
console.log('步骤2');  // 必须等待doSomething()执行完成后,才打印 步骤2

异步编程

异步编程类似于系统中断,当前进程外部的实体可以触发代码执行,即允许某些操作在后台执行,而主线程可以继续执行其他任务。

console.log('开始');
fetchDataAsync(function(data) {  // 异步操作
    console.log('数据获取完成:', data);
});
console.log('结束');

上述代码的执行结果是:

1、打印 开始
2、打印 结束
3、打印 数据获取完成

即: console.log('结束'); 代码会在 console.log('开始'); 执行完成后立即执行,不用等待 fetchDataAsync 方法执行完成。

早期的异步编程

定时器:setTimeout & setInterval

定时器 setTimeoutsetInterval 应该是应用的最早的,也是最广泛的异步API,至今仍在使用。

setTimeout:延迟执行
console.log('开始执行');

// setTimeout的基本使用
setTimeout(function () {
    console.log('1秒后执行');
}, 1000);

console.log('立即执行');

// 清除定时器
const timerId = setTimeout(() => {
    console.log('这个不会执行');
}, 2000);

clearTimeout(timerId);

上述代码的执行结果是:

1、打印 开始执行
2、打印 立即执行
3、打印 1秒后执行

值得注意的是:const timerId = setTimeout(() => {}) 这段代码中,console.log('这个不会执行'); 这段代码永远不会执行,也不会打印,这是因为 clearTimeout() 函数立即取消了定时器,所以回调函数根本没有机会执行。

setInterval:间隔执行
let count = 0;

// 每隔1秒执行一次
const intervalId = setInterval(() => {
    count++;
    console.log(count);

    if (count >= 5) {
        clearInterval(intervalId); // 清除定时器
        console.log('定时器已停止');
    }
}, 1000);

上述代码执行结果是:每秒执行1次,执行5次后,打印 定时器已停止 ,程序结束。

Node.js风格的错误优先回调

这是Node.js早期API的标准模式,影响了整个Node.js生态系统。

const fs = require('fs');

// 经典的错误优先回调模式
fs.readFile('example.txt', 'utf8', function(err, data) {
    if (err) {
        // 错误处理
        console.error('读取文件失败:', err.message);
        return;
    }
    
    // 成功处理
    console.log('文件内容:', data);
    
    // 嵌套回调:开始回调地狱
    fs.writeFile('output.txt', data.toUpperCase(), function(err) {
        if (err) {
            console.error('写入文件失败:', err);
            return;
        }
        
        console.log('文件写入成功');
        
        // 更多嵌套...
        fs.readFile('output.txt', 'utf8', function(err, newData) {
            if (err) {
                console.error('再次读取失败:', err);
                return;
            }
            console.log('转换后的内容:', newData);
        });
    });
});

眼尖的同学应该已经发现,上述代码开始产生回调地狱了。

Promise

为了解决回调模式的缺陷,Promise在2015年作为ES6规范的一部分正式加入JavaScript语言。它代表了一个异步操作的最终完成(或失败)及其结果值。

Promise A+规范

Promise A+规范是JavaScript Promise的行业标准,它详细定义了Promise的行为,确保了所有Promise实现之间的互操作性,即Promise类型。其核心目标是:

  • 互操作性:不同实现的Promise可以无缝协作。
  • 可预测性:Promise的行为在任何实现中都是一致的。
  • 可靠性:提供健壮的异步编程基础。

Promise核心概念:三大状态与两大结果

理解Promise,首先要掌握它的状态机模型。一个Promise对象有三种状态:pending(待定)fulfilled(兑现)rejected(拒绝)。通过Promise的状态来代表是否完成,pending表示尚未开始或正在执行,fulfilled表示成功完成,rejected表示没有成功完成,即失败。

注:对于 fulfilled 这一状态,不同资料翻译的结果不太一样,有的资料也将其称为“解决”,即resolved。

我们来看一个简单的Promise示例:

// Promise的三种状态:
// 1. pending(进行中): 初始状态
// 2. fulfilled(已成功): resolve()被调用
// 3. rejected(已失败): reject()被调用

// 创建Promise对象
const myPromise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        const success = true; // 模拟操作是否成功
        
        if (success) {
            resolve('操作成功!');  // 状态变为fulfilled
        } else {
            reject(new Error('操作失败!'));  // 状态变为rejected
        }
    }, 1000);
});

Promise的两个关键特性:

  • 状态不可逆:一旦状态从pending变为fulfilled或rejected,就永远不会再改变。
  • 值不可变:一旦Promise被决议(settled),它的值就固定不变了。

关于这两个特性,在后面的文章中会详细讲解。

Promise核心API

本篇只是简单介绍Promise中各个API,各API的详解及源码解析在后面的文章中会详细介绍。

API类型作用描述
构造函数静态创建新的Promise实例,包装异步操作,将回调转为Promise
.then()实例方法添加fulfilled和rejected状态回调,处理异步成功/失败结果,链式调用核心
.catch()实例方法添加rejected状态回调(错误处理),捕获Promise链中的错误
.finally()实例方法无论成功失败都会执行的回调,用于清理资源,重置状态
Promise.resolve()静态方法创建已解决的Promise ,将值转为fulfilled,快速创建成功状态
Promise.reject()静态方法创建已拒绝的Promise,将值转为rejected,快速创建失败状态
Promise.all()静态方法所有Promise都执行成功才算成功(并行),有一个失败都算失败
Promise.allSettled()静态方法所有Promise执行完成后返回结果,需要知道每个操作结果(无论成败)
Promise.race()静态方法第一个完成的Promise决定结果(竞速)
Promise.any()静态方法第一个成功的Promise决定结果 多源请求,任一成功即可

结语

本文主要介绍了Promise的相关概念,对于文章中错误的地方或者有任何问题,欢迎在评论区留言分享!