0基础进大厂,第13天:Promise:你先等等我

深入理解 ES6 中的 Promise:异步编程的利器

在 JavaScript 的世界里,异步编程一直是一个重要且具有挑战性的领域。随着前端应用的日益复杂,处理异步操作变得愈发关键。ES6 引入的 Promise,为我们提供了一种更为优雅、强大的方式来管理异步操作,极大地改善了异步代码的可读性和可维护性。好的,现在就来聊聊 Promise

从回调地狱说起

在 Promise 出现之前,JavaScript 主要通过回调函数来处理异步操作。例如,使用setTimeout模拟异步任务:

javascript 复制代码
setTimeout(() => {
    console.log('第一个异步操作完成');
    setTimeout(() => {
        console.log('第二个异步操作完成');
        setTimeout(() => {
            console.log('第三个异步操作完成');
        }, 1000);
    }, 1000);
}, 1000);

这种层层嵌套的回调函数,在处理复杂异步逻辑时,会导致代码可读性极差,维护和调试都变得异常困难,这就是著名的 "回调地狱"。

再比如,在进行多个 AJAX 请求,且后一个请求依赖前一个请求的结果时,回调地狱的问题会更加突出:

javascript 复制代码
$.get('url1', function(data1) {
    $.get(data1.nextUrl, function(data2) {
        $.get(data2.nextUrl, function(data3) {
            console.log(data3);
        });
    });
});

随着异步操作的增多,代码会像金字塔一样层层嵌套,变得难以理解和维护。

Promise 的诞生

为了解决回调地狱的问题,Promise 应运而生。Promise 是 ES6 引入的一个对象,它代表了一个异步操作的最终完成(或失败)及其结果值。简单来说,Promise 就像是一个容器,里面保存着某个未来才会结束的事件(通常是异步操作)的结果。从语法上讲,Promise 是一个对象,通过它可以获取异步操作的消息,并且提供了统一的 API,使得各种异步操作都能用同样的方式进行处理。

Promise 有三种状态:

  • Pending(等待态) :这是 Promise 的初始状态,此时异步操作尚未完成,既没有被兑现(成功)也没有被拒绝(失败)。
  • Fulfilled(成功态) :当异步操作成功完成时,Promise 的状态会变为 Fulfilled,意味着操作已经成功,我们可以从 Promise 中获取到预期的结果。此时,Promise 会携带一个值,这个值就是异步操作成功的结果。
  • Rejected(失败态) :当异步操作失败时,Promise 的状态会变为 Rejected,此时,Promise 会携带一个原因,表示失败的原因。

一个 Promise 一旦从 Pending 状态变为 Fulfilled 或 Rejected,就不可以再进行其他状态转变,即 Promise 的状态只能改变一次。

Promise 的基本使用

创建 Promise 对象

使用new Promise()构造函数来创建一个 Promise 对象。构造函数接受一个带有两个参数的函数,分别是resolve和reject。resolve用于表示操作成功,reject用于表示操作失败。

javascript 复制代码
const myPromise = new Promise((resolve, reject) => {
    // 模拟异步操作,这里使用setTimeout
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('操作成功'); // 将Promise状态置为Fulfilled,并传递成功的值
        } else {
            reject('操作失败'); // 将Promise状态置为Rejected,并传递失败的原因
        }
    }, 1000);
});

在上述代码中,我们创建了一个 Promise 对象myPromise。在Promise的执行器函数中,通过setTimeout模拟了一个异步操作。1 秒后,根据success的值,调用resolve或reject方法来改变 Promise 的状态。

处理 Promise 的结果

通过then方法来处理 Promise 成功或失败的情况。then方法接收两个回调函数作为参数,第一个回调函数在 Promise 状态变为fulfilled时调用,第二个回调函数(可选)在 Promise 状态变为rejected时调用。

javascript 复制代码
myPromise.then((result) => {
    console.log(result); // 输出:操作成功
}, (error) => {
    console.error(error); // 若操作失败,输出错误信息
});

我们也可以使用catch方法专门处理 Promise 失败的情况,它实际上是then(null, onRejected)的语法糖。

javascript 复制代码
myPromise.then((result) => {
    console.log(result); // 输出:操作成功
}).catch((error) => {
    console.error(error); // 若操作失败,输出错误信息
});

Promise 的链式调用

then方法的一个重要特性是它返回一个新的 Promise,这使得我们可以进行链式调用。通过链式调用,我们可以将多个异步操作串联起来,每个then方法处理前一个 Promise 的结果,并返回一个新的 Promise 供下一个then方法处理。

假设我们有一个需求,需要先获取用户信息,然后根据用户信息获取用户的订单列表,最后统计订单的总金额。使用then方法的链式调用可以很方便地实现这个需求:

javascript 复制代码
// 模拟获取用户信息的异步操作
function getUserInfo() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const user = { id: 1, name: '张三' };
            resolve(user);
        }, 1000);
    });
}
// 模拟根据用户信息获取订单列表的异步操作
function getOrderList(user) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const orders = [
                { id: 1, amount: 100 },
                { id: 2, amount: 200 }
            ];
            resolve(orders);
        }, 1000);
    });
}
// 模拟统计订单总金额的异步操作
function calculateTotalAmount(orders) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            const total = orders.reduce((sum, order) => sum + order.amount, 0);
            resolve(total);
        }, 1000);
    });
}
getUserInfo()
   .then(getOrderList)
   .then(calculateTotalAmount)
   .then((total) => {
        console.log('订单总金额为:', total); // 输出:订单总金额为: 300
    })
   .catch((error) => {
        console.error('发生错误:', error);
    });

在这个示例中,getUserInfo返回一个 Promise,当这个 Promise 成功时,then方法会调用getOrderList并传入用户信息,getOrderList又返回一个 Promise,当这个 Promise 成功时,下一个then方法会调用calculateTotalAmount并传入订单列表,最后计算出订单总金额并打印。如果在任何一个步骤中出现错误,catch方法会捕获并处理错误。

Promise 的静态方法

Promise.all

Promise.all方法用于将多个 Promise 对象包装成一个新的 Promise 对象。只有当所有传入的 Promise 对象都成功时,新的 Promise 对象才会成功,其结果是一个包含所有成功结果的数组;只要有一个 Promise 对象失败,新的 Promise 对象就会失败,其结果是第一个失败的 Promise 对象的错误信息。

假设我们需要同时发起多个 AJAX 请求,获取多个数据,然后在所有请求都完成后进行统一处理,就可以使用Promise.all:

javascript 复制代码
const promise1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('结果1');
    }, 1000);
});
const promise2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('结果2');
    }, 1500);
});
const promise3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('操作失败');
    }, 2000);
});
Promise.all([promise1, promise2, promise3])
   .then((results) => {
        console.log(results); // 不会执行,因为promise3失败
    })
   .catch((error) => {
        console.error(error); // 输出:操作失败
    });

在这个例子中,Promise.all接收一个包含三个 Promise 对象的数组。由于promise3失败,所以新的 Promise 对象立即失败,并捕获到promise3的错误信息。

Promise.race

Promise.race方法同样用于将多个 Promise 对象包装成一个新的 Promise 对象。与Promise.all不同的是,只要传入的 Promise 对象中有一个率先改变状态(成功或失败),新的 Promise 对象就会立即改变状态,其结果就是第一个改变状态的 Promise 对象的结果。

javascript 复制代码
const promise4 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('结果4');
    }, 2000);
});
const promise5 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('操作失败');
    }, 1000);
});
Promise.race([promise4, promise5])
   .then((result) => {
        console.log(result); // 不会执行,因为promise5率先失败
    })
   .catch((error) => {
        console.error(error); // 输出:操作失败
    });

在这个例子中,promise5在 1 秒后率先失败,所以Promise.race返回的新 Promise 对象立即失败,并捕获到promise5的错误信息。

实际案例应用

图片加载

在网页开发中,经常需要加载多张图片。我们可以使用 Promise 来管理图片的加载过程,确保所有图片都加载完成后再进行下一步操作。

javascript 复制代码
function loadImage(url) {
    return new Promise((resolve, reject) => {
        const img = new Image();
        img.src = url;
        img.onload = () => {
            resolve(img);
        };
        img.onerror = () => {
            reject(new Error('图片加载失败'));
        };
    });
}
const urls = [
    'image1.jpg',
    'image2.jpg',
    'image3.jpg'
];
Promise.all(urls.map(loadImage))
   .then((images) => {
        // 所有图片都加载完成,进行下一步操作,比如将图片添加到页面
        const container = document.createElement('div');
        images.forEach((image) => {
            container.appendChild(image);
        });
        document.body.appendChild(container);
    })
   .catch((error) => {
        console.error(error);
    });

在这个案例中,我们定义了一个loadImage函数,它返回一个 Promise,用于加载一张图片。通过Promise.all和map方法,我们同时加载多张图片,并在所有图片都加载完成后,将它们添加到页面中。

AJAX 请求

在实际项目中,AJAX 请求是非常常见的异步操作。使用 Promise 可以更好地管理 AJAX 请求的流程和错误处理。

javascript 复制代码
function ajax(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.onreadystatechange = () => {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                } else {
                    reject(new Error('请求失败'));
                }
            }
        };
        xhr.send();
    });
}
ajax('data.json')
   .then((data) => {
        console.log(data);
    })
   .catch((error) => {
        console.error(error);
    });

在这个例子中,ajax函数返回一个 Promise,用于发送 AJAX 请求。通过then和catch方法,我们可以方便地处理请求成功和失败的情况。

总结

Promise 为 JavaScript 的异步编程带来了极大的便利,它通过链式调用和统一的错误处理机制,有效地解决了回调地狱的问题,使异步代码更加清晰、易读、易维护。掌握 Promise 的使用,不仅可以提升我们编写异步代码的能力,还能更好地理解和运用其他与异步相关的技术,如async/await等。在现代前端开发中,Promise 已经成为了处理异步操作的核心机制之一,无论是使用原生的 JavaScript 进行开发,还是使用各种前端框架(如 Vue、React 等),都离不开 Promise。

相关推荐
胡gh几秒前
如何聊懒加载,只说个懒可不行
前端·react.js·面试
Double__King3 分钟前
巧用 CSS 伪元素,让背景图自适应保持比例
前端
Mapmost5 分钟前
【BIM+GIS】BIM数据格式解析&与数字孪生适配的关键挑战
前端·vue.js·three.js
一涯6 分钟前
写一个Chrome插件
前端·chrome
鹧鸪yy13 分钟前
认识Node.js及其与 Nginx 前端项目区别
前端·nginx·node.js
跟橙姐学代码13 分钟前
学Python必须迈过的一道坎:类和对象到底是什么鬼?
前端·python
汪子熙15 分钟前
浏览器里出现 .angular/cache/19.2.6/abap_test/vite/deps 路径究竟说明了什么
前端·javascript·面试
Benzenene!17 分钟前
让Chrome信任自签名证书
前端·chrome
yangholmes888817 分钟前
如何在 web 应用中使用 GDAL (二)
前端·webassembly