JS `Promise` 基础:异步操作的优雅管理

各位观众老爷们,掌声欢迎来到今天的Promise专场!我是你们的老朋友,码农张三,今天咱们不聊妹子,不聊八卦,就聊聊这Promise,这玩意儿,用好了,那是异步操作的丝滑小棉袄,用不好,那就是让你debug到怀疑人生的罪魁祸首。

准备好了吗?Let’s dive in!

第一章:Promise,你到底是个什么玩意儿?

首先,咱们得搞清楚Promise这东西到底是个什么来头。 你可以把它想象成一个承诺,承诺将来会给你一个结果,这个结果可能是成功(resolve),也可能是失败(reject)。 在这结果出来之前,Promise的状态是 pending(等待)。

举个例子,你去饭店点了个菜,服务员说:“您稍等,这菜得现做。” 这就是Promise的 pending 状态。 你也不知道这菜啥时候能上,也不知道做出来好不好吃。

如果菜做好了,端上来了,而且很好吃,那就是 resolve(成功) 了,你吃得心满意足。

如果菜做砸了,糊了,或者根本没材料了,那就是 reject(失败) 了,你只能换个菜或者饿肚子。

所以,Promise 就是一个代表异步操作最终完成(或失败)的一个对象。

第二章:Promise 的三种状态

Promise 有三种状态,这是理解 Promise 的基础:

状态 描述
pending 初始状态,既不是成功也不是失败。 就像你刚点完菜,厨师还没开始做,你也不知道会发生什么。
fulfilled 操作成功完成。 菜做好了,端上来了,而且很好吃。 对应 resolve() 方法
rejected 操作失败。 菜做砸了,或者根本没材料了。 对应 reject() 方法

注意:Promise 的状态一旦改变,就不会再变了。 也就是说,一个 Promise 要么 resolve,要么 reject,不会出现一会儿成功一会儿失败的情况。 这就像你点的菜,要么做好了,要么做砸了,不可能一会儿好吃一会儿难吃,除非你点的菜是薛定谔的菜。

第三章:创建 Promise:new Promise()

要使用 Promise,首先要创建一个 Promise 对象。 创建 Promise 对象很简单,使用 new Promise() 构造函数:

const myPromise = new Promise((resolve, reject) => {
  // 这里放你的异步操作,比如网络请求、定时器等等

  // 如果操作成功,调用 resolve(),并将结果作为参数传递进去
  // 例如:
  // resolve('菜做好了,好吃!');

  // 如果操作失败,调用 reject(),并将错误信息作为参数传递进去
  // 例如:
  // reject('菜做砸了!');
});

new Promise() 构造函数接收一个函数作为参数,这个函数被称为 executor(执行器)。 executor 函数接收两个参数:resolvereject,它们都是函数。

  • resolve(value):将 Promise 的状态改为 fulfilled,并将 value 作为结果传递给后续的 then() 方法。
  • reject(reason):将 Promise 的状态改为 rejected,并将 reason 作为错误信息传递给后续的 catch() 方法。

第四章:处理 Promise 的结果:.then() 和 .catch()

创建了 Promise 对象之后,我们需要处理 Promise 的结果。 这就要用到 .then().catch() 方法了。

  • .then(onFulfilled, onRejected):用于处理 Promise 成功(fulfilled)的情况。 它接收两个参数:onFulfilledonRejected,都是函数。 onFulfilled 在 Promise 成功时被调用,接收 Promise 的结果作为参数。 onRejected 在 Promise 失败时被调用,接收 Promise 的错误信息作为参数。 onRejected 可以省略,不建议省略,容易造成错误被忽略的情况。

  • .catch(onRejected):用于处理 Promise 失败(rejected)的情况。 它接收一个参数:onRejected,是一个函数。 onRejected 在 Promise 失败时被调用,接收 Promise 的错误信息作为参数。

const myPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const randomNumber = Math.random();
    if (randomNumber > 0.5) {
      resolve(`成功啦!随机数是:${randomNumber}`);
    } else {
      reject(`失败啦!随机数是:${randomNumber}`);
    }
  }, 1000); // 模拟一个 1 秒的异步操作
});

myPromise
  .then(
    (result) => {
      console.log('Promise 成功了:', result);
    },
    (error) => {
      console.error('Promise 失败了(在then中处理):', error);
    }
  )
  .catch((error) => {
    console.error('Promise 失败了(在catch中处理):', error);
  });

console.log("代码继续执行,不会阻塞!");

在这个例子中,我们创建了一个 Promise 对象,它会在 1 秒后随机 resolve 或 reject。 如果随机数大于 0.5,Promise 就 resolve,否则就 reject。

我们使用 .then() 方法来处理 Promise 成功和失败的情况。 同时我们也使用了.catch()方法来捕获异常,注意这里.then()的第二个参数和.catch()作用一样,但是推荐使用.catch(),便于阅读和维护。

第五章:Promise 链:优雅地处理多个异步操作

Promise 最强大的地方在于它可以链式调用 .then() 方法,从而优雅地处理多个异步操作。

const fetchUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const userData = { id: 123, name: '张三' };
      resolve(userData);
    }, 500);
  });
};

const fetchUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const userPosts = [
        { id: 1, title: 'Promise 入门' },
        { id: 2, title: 'Async/Await 进阶' },
      ];
      resolve(userPosts);
    }, 500);
  });
};

fetchUserData()
  .then((userData) => {
    console.log('获取到用户信息:', userData);
    return fetchUserPosts(userData.id); // 返回一个新的 Promise
  })
  .then((userPosts) => {
    console.log('获取到用户文章:', userPosts);
  })
  .catch((error) => {
    console.error('出错了:', error);
  });

在这个例子中,我们先使用 fetchUserData() 函数获取用户信息,然后使用 fetchUserPosts() 函数获取用户的文章。 每个 .then() 方法都返回一个新的 Promise,这样就可以将多个异步操作串联起来。 这种链式调用使得代码更加清晰易懂,也更容易维护。

第六章:Promise.all():并行处理多个异步操作

有时候,我们需要同时执行多个异步操作,并且在所有操作都完成后再进行下一步处理。 这时候就可以使用 Promise.all() 方法。

Promise.all() 接收一个 Promise 数组作为参数,返回一个新的 Promise。 这个新的 Promise 会在所有 Promise 都 resolve 时 resolve,并将所有 Promise 的结果以数组的形式返回。 如果其中任何一个 Promise reject,这个新的 Promise 就会立即 reject,并将 reject 的原因返回。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 1 完成');
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 2 完成');
  }, 500);
});

const promise3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 3 完成');
  }, 1500);
});

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    console.log('所有 Promise 都完成了:', results); // 输出:['Promise 1 完成', 'Promise 2 完成', 'Promise 3 完成']
  })
  .catch((error) => {
    console.error('有 Promise 失败了:', error);
  });

在这个例子中,我们创建了三个 Promise 对象,它们分别在 1 秒、0.5 秒和 1.5 秒后 resolve。 Promise.all() 会等待所有 Promise 都 resolve 后,才执行 .then() 方法。

第七章:Promise.race():赛跑模式

Promise.race() 方法与 Promise.all() 类似,也接收一个 Promise 数组作为参数,返回一个新的 Promise。 但是,Promise.race() 的行为却截然不同。

Promise.race() 返回的 Promise 会在第一个 Promise resolve 或 reject 时立即 resolve 或 reject,并将第一个 Promise 的结果或错误信息返回。 也就是说,Promise.race() 就像一场赛跑,谁先跑完,就算谁赢。

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('Promise 1 完成');
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject('Promise 2 失败');
  }, 500);
});

Promise.race([promise1, promise2])
  .then((result) => {
    console.log('第一个完成的 Promise 是:', result); // 不会执行到这里,因为 promise2 失败了
  })
  .catch((error) => {
    console.error('第一个失败的 Promise 是:', error); // 输出:'第一个失败的 Promise 是:Promise 2 失败'
  });

在这个例子中,promise2 在 0.5 秒后 reject,所以 Promise.race() 会立即 reject,并将 promise2 的错误信息返回。

第八章:Promise 的一些小技巧

  • 使用 Promise.resolve()Promise.reject() 快速创建已完成或已失败的 Promise。

    const resolvedPromise = Promise.resolve('立即完成!');
    const rejectedPromise = Promise.reject('立即失败!');
    
    resolvedPromise.then(result => console.log(result)); // 输出:'立即完成!'
    rejectedPromise.catch(error => console.error(error)); // 输出:'立即失败!'
  • 避免在 executor 函数中直接返回值。 应该使用 resolve()reject() 来改变 Promise 的状态。

    // 错误示例:
    const badPromise = new Promise((resolve, reject) => {
      return '错误的值'; // 这样写不会改变 Promise 的状态
    });
    
    badPromise.then(result => console.log(result)); // 不会执行到这里
    
    // 正确示例:
    const goodPromise = new Promise((resolve, reject) => {
      resolve('正确的值'); // 这样写会改变 Promise 的状态
    });
    
    goodPromise.then(result => console.log(result)); // 输出:'正确的值'
  • 使用 finally() 方法在 Promise 完成后执行一些清理工作,无论 Promise 是 resolve 还是 reject。

    const myPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        Math.random() > 0.5 ? resolve('成功') : reject('失败');
      }, 500);
    });
    
    myPromise
      .then(result => console.log(result))
      .catch(error => console.error(error))
      .finally(() => {
        console.log('无论成功还是失败,都会执行这里');
      });

第九章:Promise 与 Async/Await:更优雅的异步语法糖

Async/Await 是 ES2017 引入的,用于简化异步操作的语法糖。 它可以让你像写同步代码一样写异步代码,大大提高了代码的可读性和可维护性。

Async/Await 实际上是基于 Promise 实现的,所以理解 Promise 是理解 Async/Await 的基础。

  • async 函数: 在函数声明前加上 async 关键字,表示这是一个异步函数。 async 函数会自动返回一个 Promise 对象。

  • await 表达式: 在 async 函数中使用 await 关键字,可以暂停函数的执行,等待 Promise resolve。 await 表达式会返回 Promise 的结果。

const fetchData = async () => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    const data = await response.json();
    console.log('获取到的数据:', data);
    return data;
  } catch (error) {
    console.error('出错了:', error);
    throw error; // 重新抛出错误,方便外部捕获
  }
};

fetchData()
  .then(data => console.log('最终的数据:', data))
  .catch(error => console.error('外部捕获的错误:', error));

在这个例子中,我们使用 async 关键字声明了一个异步函数 fetchData()。 在 fetchData() 函数中,我们使用 await 关键字等待 fetch()response.json() 方法返回的 Promise resolve。 try...catch 块用于捕获异步操作中的错误。

Async/Await 使得异步代码更加简洁易懂,也更容易调试。

第十章:总结:Promise,异步世界的瑞士军刀

Promise 是 JavaScript 中处理异步操作的重要工具。 它可以让你更优雅地管理异步操作,避免回调地狱,提高代码的可读性和可维护性。

掌握 Promise 的基础知识,包括 Promise 的三种状态、.then().catch() 方法、Promise 链、Promise.all()Promise.race() 方法,以及 Async/Await 语法糖,你就可以在异步世界里游刃有余,写出更加健壮和高效的代码。

记住,熟能生巧,多练习,多实践,你也能成为 Promise 大师!

今天的Promise讲座就到这里,感谢大家的聆听,下课! 别忘了点个赞再走哦!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注