Promise 是 JavaScript 中处理异步操作的一种对象,它代表了一个异步操作的最终完成(或失败)及其结果值。Promise 是 ES6 引入的特性,用于解决传统回调函数嵌套过深导致的"回调地狱"问题。

一、Promise 的基本概念

Promise 是一个对象,它有三种状态:

  • pending(等待中):初始状态,既未成功也未失败。

  • fulfilled(已成功):操作成功完成。

  • rejected(已失败):操作失败。

状态的特点:

1. 状态只能从 pending 转变为 fulfilled 或 rejected。

2. 状态一旦改变,就会永久保持该状态,不会再发生变化。

二、Promise 的创建与使用

1. 创建 Promise
const promise = new Promise((resolve, reject) => {
  // 异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('操作成功!'); // 将状态从 pending 改为 fulfilled
    } else {
      reject('操作失败!'); // 将状态从 pending 改为 rejected
    }
  }, 1000);
});

Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolvereject

  • resolve 函数:将 Promise 对象的状态从 pending 变为 fulfilled,并将异步操作的结果作为参数传递出去。

  • reject 函数:将 Promise 对象的状态从 pending 变为 rejected,并将异步操作报出的错误作为参数传递出去。

  • 创建 Promise 对象时必须附带这个执行器回调函数,即:

// 正确:带有执行器函数

const promise = new Promise((resolve, reject) => {
  // 异步操作代码
});

// 错误:没有提供执行器函数

const invalidPromise = new Promise(); // 会抛出 TypeError
2. Promise 实例的方法
then() 方法

then() 方法用于处理 Promise 成功或失败时的回调函数,它返回一个新的 Promise 对象,因此可以链式调用。

promise.then(
  value => {
    console.log('成功:', value);
  },
  reason => {
    console.log('失败:', reason);
  }
);
catch() 方法

catch() 方法用于指定发生错误时的回调函数,它是 .then(null, rejection) 的别名。

promise
  .then(value => {
    console.log('成功:', value);
  })
  .catch(reason => {
    console.log('失败:', reason);
  });
finally() 方法

finally() 方法用于指定无论 Promise 是成功还是失败都会执行的回调函数。

promise
  .then(value => {
    console.log('成功:', value);
  })
  .catch(reason => {
    console.log('失败:', reason);
  })
  .finally(() => {
    console.log('无论成功失败都会执行');
  });

三、Promise 的静态方法

1. Promise.resolve()

创建一个立即 resolved 的 Promise 对象。

const resolvedPromise = Promise.resolve('成功');
resolvedPromise.then(value => console.log(value)); // 输出: 成功
2. Promise.reject()

创建一个立即 rejected 的 Promise 对象。

const rejectedPromise = Promise.reject('失败');
rejectedPromise.catch(reason => console.log(reason)); // 输出: 失败
3. Promise.all()

将多个 Promise 实例包装成一个新的 Promise 实例。只有当所有 Promise 都成功时,返回的 Promise 才会成功;如果有任何一个 Promise 失败,返回的 Promise 就会失败。

const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // 输出: [1, 2, 3]
});
4. Promise.race()

将多个 Promise 实例包装成一个新的 Promise 实例。只要有一个 Promise 状态发生改变(无论是成功还是失败),返回的 Promise 就会跟着改变状态。

const promise1 = new Promise(resolve => setTimeout(() => resolve('one'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('two'), 500));

Promise.race([promise1, promise2]).then(value => {
  console.log(value); // 输出: 'two',因为它更快
});
5. Promise.allSettled()

将多个 Promise 实例包装成一个新的 Promise 实例。只有当所有 Promise 都结束时(无论成功或失败),返回的 Promise 才会结束,返回所有 Promise 的结果数组。

const promise1 = Promise.resolve(1);
const promise2 = Promise.reject('错误');

Promise.allSettled([promise1, promise2]).then(results => {
  console.log(results);
  // 输出:
  // [
  //   { status: 'fulfilled', value: 1 },
  //   { status: 'rejected', reason: '错误' }
  // ]
});
6. Promise.any()

将多个 Promise 实例包装成一个新的 Promise 实例。只要有一个 Promise 成功,返回的 Promise 就会成功;只有当所有 Promise 都失败时,返回的 Promise 才会失败。

const promise1 = Promise.reject('错误1');
const promise2 = Promise.resolve('成功');
const promise3 = Promise.reject('错误3');

Promise.any([promise1, promise2, promise3]).then(value => {
  console.log(value); // 输出: '成功'
});

四、Promise 解决回调地狱问题

传统的回调函数嵌套可能会导致代码难以阅读和维护:

// 回调地狱示例
asyncOperation1(function(result1) {
  asyncOperation2(result1, function(result2) {
    asyncOperation3(result2, function(result3) {
      // 更多嵌套回调...
    });
  });
});

使用 Promise 链式调用可以大大提高代码的可读性:

// Promise 链式调用示例
asyncOperation1()
  .then(result1 => asyncOperation2(result1))
  .then(result2 => asyncOperation3(result2))
  .then(result3 => {
    // 处理最终结果
  })
  .catch(error => {
    // 处理所有错误
  });

五、Promise 的实际应用场景

1. 处理网络请求

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('请求失败:', error));

2. 定时器操作

const delay = ms => new Promise(resolve => setTimeout(resolve, ms));

delay(2000)
  .then(() => console.log('延迟了2秒'));

3. 并行异步操作

const fetchUser = () => fetch('https://api.example.com/user');
const fetchPosts = () => fetch('https://api.example.com/posts');

Promise.all([fetchUser(), fetchPosts()])
  .then(([userResponse, postsResponse]) => Promise.all([userResponse.json(), postsResponse.json()]))
  .then(([userData, postsData]) => {
    console.log('用户数据:', userData);
    console.log('文章数据:', postsData);
  });

总结

Promise 是 JavaScript 处理异步操作的强大工具,它通过链式调用解决了回调地狱问题,使异步代码更加清晰易读。掌握 Promise 的各种方法和用法,对于编写现代 JavaScript 应用至关重要。在实际开发中,Promise 通常与 async/await 结合使用,可以进一步简化异步代码的编写。

下面是关于async/await的相关内容。

在实际开发中,async/await 是 ES2017 引入的语法糖,它基于 Promise,能够让异步代码的写法更接近同步代码,极大提高了代码的可读性和可维护性。下面通过示例来说明它们的结合使用:

1. 基本用法

// 1. 定义一个返回 Promise 的函数
function fetchData(url) {
  return new Promise((resolve, reject) => {
    // 模拟网络请求
    setTimeout(() => {
      if (url) {
        resolve(`数据来自 ${url}`);
      } else {
        reject(new Error('URL不能为空'));
      }
    }, 1000);
  });
}

// 2. 使用 async/await 简化异步代码
async function getData() {
  try {
    // 使用 await 等待 Promise 完成
    const result = await fetchData('https://api.example.com');
    console.log(result); // 输出: 数据来自 https://api.example.com
    return result;
  } catch (error) {
    // 错误处理更直观
    console.error('获取数据失败:', error.message);
  }
}

// 调用异步函数
getData();

2. 串行执行多个异步操作

async function fetchMultipleData() {
  try {
    // 串行执行,第二个请求依赖第一个请求的结果
    const userData = await fetchData('https://api.example.com/users');
    console.log('用户数据获取完成');
    
    // 基于用户数据获取订单数据
    const orderData = await fetchData(`https://api.example.com/orders?user=${userData.id || 'default'}`);
    console.log('订单数据获取完成');
    
    return { userData, orderData };
  } catch (error) {
    console.error('获取数据失败:', error);
  }
}

fetchMultipleData();

3. 并行执行多个异步操作

async function fetchParallelData() {
  try {
    // 使用 Promise.all 并行执行多个异步操作
    const [userData, productData, commentData] = await Promise.all([
      fetchData('https://api.example.com/users'),
      fetchData('https://api.example.com/products'),
      fetchData('https://api.example.com/comments')
    ]);
    
    console.log('所有数据获取完成');
    return { userData, productData, commentData };
  } catch (error) {
    console.error('至少有一个请求失败:', error);
  }
}

fetchParallelData();

4. 实际开发场景示例(模拟用户登录流程)

// 模拟登录 API
function login(username, password) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (username === 'admin' && password === '123456') {
        resolve({ token: 'jwt-token-123', userId: 1 });
      } else {
        reject(new Error('用户名或密码错误'));
      }
    }, 800);
  });
}

// 模拟获取用户信息 API
function getUserInfo(userId, token) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (token && userId) {
        resolve({
          id: userId,
          name: '管理员',
          role: 'admin',
          permissions: ['read', 'write', 'delete']
        });
      } else {
        reject(new Error('无效的认证信息'));
      }
    }, 600);
  });
}

// 使用 async/await 简化登录流程
async function userLoginProcess(username, password) {
  try {
    // 1. 登录认证
    console.log('开始登录...');
    const loginResult = await login(username, password);
    console.log('登录成功,获取用户信息...');
    
    // 2. 获取用户详情
    const userInfo = await getUserInfo(loginResult.userId, loginResult.token);
    console.log('用户信息获取成功');
    
    // 3. 返回完整结果
    return { ...loginResult, ...userInfo };
  } catch (error) {
    console.error('登录流程失败:', error.message);
    throw error; // 可以选择继续抛出错误,让调用者处理
  }
}

// 调用登录流程
userLoginProcess('admin', '123456')
  .then(result => {
    console.log('登录流程完成,用户数据:', result);
  })
  .catch(error => {
    console.log('最终错误处理:', error);
  });

5. async/await 的优势总结

1. 代码更简洁:不需要嵌套的 .then().catch() 链。

2. 错误处理更直观:可以使用传统的 try/catch 结构。

3. 流程控制更清晰:异步代码的执行顺序一目了然。

4. 调试更方便:可以像调试同步代码一样设置断点。

5. 条件和循环处理更自然:在异步代码中使用条件判断和循环结构更加直观。

 

通过这些示例可以看出,async/await 让异步代码的编写和阅读变得更加自然,是现代 JavaScript 开发中处理异步操作的首选方式。