Promise到底是什么、怎么用

一、什么是Promise

Promise 是 ES6 引入的异步编程解决方案,用于处理异步操作的结果。它代表了一个未来才会完成(或失败)的操作及其结果值。核心概念:Promise 是一个对象,表示异步操作的最终完成(或失败)从"pending"(进行中)变为"fulfilled"(已成功)或"rejected"(已失败)一旦状态改变,就不会再变(不可逆)

核心概念

  • Promise是一个对象,表示异步操作的最终完成(或失败)
  • 从"pending"(进行中) 变为 "fulfilled"(已成功) 或 "rejected"(已失败)
  • 一旦状态改变,就不会再变(不可逆)

二、Promise的三种状态

状态 说明 触发方式
Pending 初始状态,未决 创建 Promise 时
Fulfilled 操作成功完成 调用 resolve(value)
Rejected 操作失败 调用 reject(reason)

特点

  • 状态一旦改变就不逆
  • 只能从 Pending -> Fulfilled 或 Pending -> Rejected
  • 改变后会立即触发相应的回调
  • 不管执行结果如何,一定会有一个最终状态。

三、Promise的核心方法

名称 说明 触发方式
.then() 处理成功和失败 promise.then()
.catch() 捕获错误 promise.catch()
.finally() 最终处理(不管状态如何,都会执行) promise.finally()

四、基本语法

javascript 复制代码
// new Promise() 构造函数中有一个回调函数,回调函数中有2个参数分别为 resolve reject函数,根据Promise的状态,执行对应的回调。
const promise = new Promise((resolve, reject) => {
    if(false){
        resolve('Fulfilled')  // 将状态改为 Fulfilled
    } else {
        reject('Rejected')   // 将状态改为 Rejected
    }

})

promise
    .then(result => {
        // 处理成功的结果
        console.log('成功:' , result)
        // 打印结果 'Fulfilled'
    })
    .catch(error => {
        console.log('失败:', error)
        // 打印结果 'Rejected'
    })
    .finally(() => {
    // 无论成功、失败都会执行,和axios库中的接口调用一样,都会执行的。
        console.log('不管状态如何,都会执行')
    })

五、完整示例

  1. 模拟异步请求
javascript 复制代码
// 创建一个模拟异步请求的 Promise 
function fetchData(id){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(id > 0) {
                resolve({id: id, name: '数据' + id })
            } else {
                reject("无效的id")
            }
    
        }, 1000)
    
    })
}

fetchData(1) 
    .then(data => {
        console.log('数据获取成功', data)
        // 输出: 获取成功: { id: 1, name: '数据1' }
    })
    .catch(error => {
        console.log('获取失败', error)
    })
  1. 读取文件(Node.js)
javascript 复制代码
const fs = require('fs')
function readFileAsync(path) {
    return new Promise((resolve, reject) => {
        fs.readFile(path, 'utf8', (err, data) => {
            if(err) {
                reject(err)
            } else {
                resolve(data)
            }
        })
    })
}
readFileAsync
    .then(data) {
        console.log('文件内容:', data)

     }
    .catch(err) {
        console.log('读取失败:', err )
    }
  1. AJAX 请求封装
javascript 复制代码
// 使用Promise封装ajax请求的好处:1.可以链式调用。2.避免回调地狱
function ajax(url, method = 'GET', data = null){ 
    return new Promise((resolve, reject) => { 
        const xhr = new XMLHttpRequest(); 
        xhr.open(method, url); 
        xhr.setRequestHeader('Content-Type', 'application/json'); 
        xhr.onload = () => { 
            if (xhr.status >= 200 && xhr.status < 300) { 
                resolve(JSON.parse(xhr.responseText)); } 
            else { 
                reject(new Error(`HTTP Error: ${xhr.status}`)); 
             } 
        }; 
        xhr.onerror = () => { 
            reject(new Error('Network Error')); 
        }; 
        xhr.send(data ? JSON.stringify(data) : null); }); 
} 
// 使用 
ajax('https://api.example.com/users') 
    .then(users => { console.log('用户列表:', users); }) 
    .catch(error => { console.error('请求失败:', error); });

六、Promise 的核心方法示例

  1. then() 处理成功和失败
javascript 复制代码
    promise.then(onFulfilled, onRejected) 

// 等价于 promise 
    .then(onFulfilled) 
    .catch(onRejected)    

示例:

javascript 复制代码
    fetchData(1)
        .then(
            data => console.log('成功:', data),     //onFulfilled
            error => console.log('失败:', error)    // onRejected
        )

链式调用:

javascript 复制代码
    fetchData(1)
        .then(data => {
            console.log('第一步:', data)
            return fetchData(2)
        })
        .then(data => {
            console.log('第二步:', data)
            return fetchData(3)
        })
        .then(data => {
            console.log('第三步:', data)
         })
         .catch(error => {
             console.log('任何一步出错:', error)
         })

输出:

javascript 复制代码
第一步: { id: 1, name: '数据1' }
第二步: { id: 2, name: '数据2' }
第三步: { id: 3, name: '数据3' }
  1. catch() 捕获错误
javascript 复制代码
promise.catch(onRejected)
// 等价于
promise.then(null, onRejected)

示例:

javascript 复制代码
    fetchData(-1) 
        .then(data => { console.log('不会执行'); }) 
        .catch(error => { console.error('捕获到错误:', error.message); // 输出: 捕获到错误: 无效的 ID });

链式中的错误捕获:

javascript 复制代码
    fetchData(1) 
        .then(data => { 
             console.log('第一步成功'); 
             return fetchData(-1); // 这里会失败 
          }) 
         .then(data => { console.log('不会执行'); })
         .catch(error => { console.error('捕获错误:', error.message); // 输出: 捕获错误: 无效的 ID });
  1. finally() 最终执行
javascript 复制代码
    promise.finally(onFinally)

特点:

  • 无论成功或失败都会执行
  • 不接受任何参数
  • 常用于清理工作

示例:

javascript 复制代码
let isLoading = true; 
fetchData(1) 
    .then(data => { console.log('数据:', data); })
    .catch(error => { console.error('错误:', error); })
    .finally(() => { isLoading = false; console.log('加载结束'); // 总是执行 });
  1. 链式调用原理

关键规则:

  • .then()总是返回一个新的 promise
  • 返回值决定下一个 .then() 的参数
  • 抛出异常会跳到 .catch()

示例解析:

javascript 复制代码
    Promise.resolve(1)
        .then(data => {
            return data + 1    // 返回2
        }) 
        .then(data => {
            console.log(data)  // 输出 2
            return data * 2   // 返回 4
        })
        .then(data => {
            console.log(data)  // 输出4
            throw new Error('出错了')  //抛出异常
        })
        .catch(error => {
            console.log('error', error)  //输出:出错了
            return 0   // 重置 data值
        })
        .then(data => {
            console.log('重置后的值:', data)  // 输出0
        })

七、Promise 的静态方法

  1. Promise.resolve() 创建已解决的 Promise
javascript 复制代码
    // 方式一 传入值
    cosnt res =  Promise.resolve(20)
    res.then(data => {
        console.log(data)  // 输出20
    })
    // 方式二 传入Promise(原样返回)
    const p2 =  Promise.resolve(Promise.resolve(20))
    console.log(p2 === p1)  // false
    

2 Promise.reject() 创建已拒绝的 Promise

javascript 复制代码
    const p = Promise.reject(new Error('出错了'))
    p.catch(error => {
        console.error(error)
    }

3 Promise.all() 并行执行多个 Promise

javascript 复制代码
    Promise.all([p1, p2])
        .then( results => {
            // results是所有请求结果的数组,按照请求顺序返回的
        })
        .catch( error => {
            // 执行错误
        })

示例:

javascript 复制代码
    const p1 = fetchData(1)
    const p2 = fetchData(2)
    Promise.all([p1, p2]).then(results => {
        console.log(results)
        // 输出数据
        // { id: 1, name: '数据1'}
        // { id: 2, name: '数据2'}
    }).catch( error => {
        console.log('error', )
    })

特点:

  • 所有Promise都成功才算成功
  • 任何一个失败就立即失败(短路)
  • 并行执行,总时间取决于最慢的那个

4 Promise.race() 竞速

javascript 复制代码
    Promise.race([p1, p2])
        .then(first => {
            // 第一个完成的 Promise 的结果
        })

示例:超时控制

javascript 复制代码
    function timeOut(ms){
        return new Promise((_, reject) => {
            setTimeout(() => reject(new Error(`超时${ms}`)), ms)
        })
    }
    Promise.race([
        fetchData(1),    // 正常请求
        timeOut(500)     // 超时请求
    ]).then(data =>  {
        console.log(data) 
        
    }).catch(error => {
        console.error('错误', error.message)
    })

5 Promise.allSettled() 等待所有完成(ES2020)

javascript 复制代码
    Promise.allSettled([p1, p2, p3])
        .then(results => {
            // results 包含所有的 Promise 的状态和结果
        })

示例:

javascript 复制代码
    const p1 = Promise.resolve(1)
    const p2 = Promise.reject(2)
    const p3 = Promise.resolve(3)
    Promise.allSettled([p1, p2, p3])
        .then( results => {
            console.log(results)
        })

    // [
        // { status: 'fulfilled', value: 1 },
        // { status: 'rejected', reason: 2 },
        // { status: 'fulfilled', value: 3 }
    // ]

// 这个示例可以 使用 Promise.all 调用一下,看看输出的内容是什么?

与 Promise.all() 的区别:

  • Promise.all: 一个失败就全部失败
  • Promise.allSettled: 等待所有完成,不管成功失败 6 Promise.any() 第一个完成的(ES2021)
javascript 复制代码
    Promise.any([p1, p2])
        .then(first => {
            // 第一个成功的 Promise 的结果
        })
        .catch(error => {
            // 所有的失败和结果返回慢的数据 都会在失败里面
         }

示例:

javascript 复制代码
    // 从多个镜像源下载,哪个快用哪个
    Promise.any([
        fetchFromMirror1(),
        fetchFromMirror2(),
        fetchFromMirror3()
    ])
    .then(data => {
        console.log('获取到数据:', data);
    })
    .catch(error => {
        console.error('所有镜像都失败了:', error);
    });

八、常见错误和陷阱

  • 错误1: 忘记return
javascript 复制代码
// ❌ 错误写法
    fetchData(1)
        .then(data => {
            console.log(data);
            fetchData(2); // 没有 return,下一个 .then() 收到 undefined
        })
        .then(data => {
            console.log(data); // undefined
        });

    // ✅ 正确写法
    fetchData(1)
        .then(data => {
            console.log(data);
            return fetchData(2); // 返回 Promise
        })
        .then(data => {
            console.log(data); // { id: 2, name: '数据2' }
        });
  • 错误2: 嵌套 Promise (回调地狱)
javascript 复制代码
// ❌ 错误写法(嵌套)

    fetchData(1)
        .then(data1 => {
            fetchData(2)
                .then(data2 => {
                    fetchData(3)
                        .then(data3 => {
                            console.log(data1, data2, data3);
                        });
                 });
           });

    // ✅ 正确写法(链式)

    fetchData(1)
        .then(data1 => {
            return fetchData(2).
                then(data2 => {
                    return fetchData(3).then(data3 => {
                        return { data1, data2, data3 };
                    });
                });
            })
        .then(({ data1, data2, data3 }) => {
            console.log(data1, data2, data3);
        });

    // ✅ 更好的写法(使用 async/await)
    async function getAllData() {
        const data1 = await fetchData(1);
        const data2 = await fetchData(2);
        const data3 = await fetchData(3);
        console.log(data1, data2, data3);

    }
  • 错误3: 错误没有被捕获
javascript 复制代码
    // ❌ 错误写法
    fetchData(-1)
        .then(data => {
            console.log(data);
        });
    // 错误被吞掉,没有任何提示

    // ✅ 正确写法
    fetchData(-1)
        .then(data => {
            console.log(data);
        })
        .catch(error => {
            console.error('捕获到错误:', error);
         });
  • 错误4: 在循环中使用 Promise
javascript 复制代码
// ❌ 串行执行(慢)
for (let i = 1; i <= 3; i++) {
    fetchData(i).then(data => console.log(data));
}
// ✅ 并行执行(快)
const promises = [];
for (let i = 1; i <= 3; i++) {
    promises.push(fetchData(i));
}
Promise.all(promises).then(results => {
    console.log(results);
});

// ✅ 或使用 map
Promise.all([1, 2, 3].map(id => fetchData(id)))
    .then(results => {
        console.log(results);
    });

九、Promise vs 回调函数

  • 回调地狱问题
javascript 复制代码
getUser(userId, (user) => {
    getPosts(user.id, (posts) => {
        getComments(posts[0].id, (comments) => {
            getAuthor(comments[0].authorId, (author) => {
                console.log(author);
            });
        });
    });
});
  • Promise 解决方案
javascript 复制代码
    // Promise 链式调用(扁平化)
    getUser(userId)
        .then(user => getPosts(user.id))
        .then(posts => getComments(posts[0].id))
        .then(comments => getAuthor(comments[0].authorId))
        .then(author => console.log(author))
        .catch(error => console.error(error));
  • async/await 更优雅
javascript 复制代码
// async/await(同步写法)
async function getAuthorInfo(userId) {
    try {
        const user = await getUser(userId);
        const posts = await getPosts(user.id);
        const comments = await getComments(posts[0].id);
        const author = await getAuthor(comments[0].authorId);
        console.log(author);
    } catch (error) {
        console.error(error);
    }
}

十、实战应用

  • 场景1: 并发请求优化
javascript 复制代码
// 需求:同时获取用户信息和商品列表
// ❌ 串行(慢)
async function getData() {
    const user = await fetchUser(); // 1s
    const products = await fetchProducts(); // 1s
    // 总共 2s
    return { user, products };
}
// ✅ 并行(快)
async function getData() {
    const [user, products] = await Promise.all([
        fetchUser(), // 1s
        fetchProducts() // 1s
    ]);
    // 总共 1s(并行执行)
    return { user, products };
}
  • 场景2: 重试机制
javascript 复制代码
function retry(fn, retries = 3, delay = 1000) {
    return new Promise((resolve, reject) => {
        function attempt(n) {
            fn()
            .then(resolve)
            .catch(error => {
                if (n <= 1) {
                    reject(error);
                } else {
                    setTimeout(() => {
                        console.log(`重试 (${retries - n + 1}/${retries})...`);
                        attempt(n - 1);
                    }, delay);
                }
            });
        }
        attempt(retries);
    });
}

// 使用

retry(() => fetchData(-1), 3, 1000)
    .then(data => console.log('成功:', data))
    .catch(error => console.error('最终失败:', error));
  • 场景3: 请求队列(限制并发数)
javascript 复制代码
class RequestQueue {
    constructor(maxConcurrent = 5) {
        this.maxConcurrent = maxConcurrent;
        this.current = 0;
        this.queue = [];
    }

    add(requestFn) {
        return new Promise((resolve, reject) => {
            this.queue.push({ requestFn, resolve, reject });
            this.process();
        });
    }
    process() {
        if (this.current >= this.maxConcurrent || this.queue.length === 0) {
            return;
        }
        this.current++;
        const { requestFn, resolve, reject } = this.queue.shift();
        requestFn()
            .then(result => {
                resolve(result);
                this.current--;
                this.process();
            })
            .catch(error => {
                reject(error);
                this.current--;
                this.process();
            });
        }
    }

// 使用
const queue = new RequestQueue(3); // 最多同时 3 个请求
[1, 2, 3, 4, 5].forEach(id => {
    queue.add(() => fetchData(id))
        .then(data => console.log(`数据${id}:`, data));
});

十一、总结

Promise 的核心优势:

特性 说明
解决回调地狱 链式调用,代码更扁平
统一错误处理 .catch() 集中捕获错误
状态不可变 一旦决议,状态不再改变
组合性强 Promise.all、race 等静态方法
标准化 ES6标准、浏览器原生支持

最佳实践:

  1. 终添加 .catch() 处理错误
  2. 用 Promise.all() 并行执行独立请求
  3. 避免嵌套 Promise,使用链式调用
  4. .then() 中记得 return
  5. 使用 async/await 简化复杂逻辑
相关推荐
IMPYLH2 小时前
HTML 的 <h1>–<h6> 元素
前端·javascript·html
WIN赢3 小时前
【抽象思想-从复杂中抽离简单、收敛的口子】
java·前端·javascript
冰暮流星5 小时前
javascript之webstorage用法
开发语言·javascript·ecmascript
执子念的飞鱼5 小时前
浏览器直接预览 Pages、Numbers、Keynote:iWork 格式真正难在哪
前端·javascript
用户345208153885 小时前
我给 DSH 写了读 10x 数据的插件
javascript
执子念的飞鱼6 小时前
70MB Excel 浏览器预览失败:定位 XLSX 重复解压与内存峰值
javascript·性能优化
12.=0.6 小时前
【REVIEW_C】【持续更新】
服务器·前端·javascript