什么是promise,为什么需要promise

198 阅读10分钟

在现代Web开发中,异步操作已经成为不可避免的一部分。无论是从服务器获取数据,还是处理用户的输入,我们都需要有效地管理这些异步任务。然而,传统的回调函数(Callback)方式容易导致代码变得复杂难懂,形成所谓的“回调地狱”(Callback Hell)。在这样的背景下,JavaScript引入了Promise这一强大的工具,帮助开发者更优雅地处理异步操作。然而promise又是什么呢,我们为什么需要使用promise呢。

JavaScript 为什么需要 Promise

JavaScript是一种单线程语言,这意味着它一次只能执行一个任务。然而,现代Web应用需要处理大量的异步操作,如网络请求、定时器、文件读取等。如果所有这些操作都在主线程上同步进行,用户界面将会被阻塞(页面卡死),用户体验将非常差。因此,JavaScript需要一种机制来处理这些异步操作,使得主线程可以继续处理其他任务。

Promise 解决了什么问题

回调地狱(Callback Hell) 在Promise之前,处理异步操作主要依赖回调函数。然而,当多个异步操作需要依赖前一个操作的结果时,回调函数会相互嵌套,代码变得难以阅读和维护,这种现象被称为回调地狱。例如:

doSomething(function(result1) {
  doSomethingElse(result1, function(result2) {
    doAnotherThing(result2, function(result3) {
      doSomethingMore(result3, function(result4) {
        // 继续嵌套回调
      });
    });
  });
});

这种嵌套结构不仅难以阅读,而且难以调试和维护。 更好的错误处理 在使用回调函数处理异步操作时,错误处理通常也需要通过回调函数来进行,这使得错误处理逻辑变得分散和混乱:

doSomething(function(error, result1) {
  if (error) {
    // 处理错误
  } else {
    doSomethingElse(result1, function(error, result2) {
      if (error) {
        // 处理错误
      } else {
        doAnotherThing(result2, function(error, result3) {
          if (error) {
            // 处理错误
          } else {
            // 继续处理
          }
        });
      }
    });
  }
});

这种方式不仅代码冗长,而且容易遗漏错误处理的情况。

场景案例

比如现在有一个电商平台项目的操作场景: image.png 用户登录-查看订单-查看订单的商品信息。这里的每个步骤都是异步操作,并且需要等待前一个步骤完成后才能进行下一个步骤。比如,我们需要依次完成以下任务:

  1. 获取用户数据
  2. 根据用户数据获取订单信息
  3. 根据订单信息获取商品详情
  4. 显示最终结果

使用传统的回调函数来处理这些异步操作时,代码可能会变成这样:

function getUserData(callback) {
  setTimeout(() => {
    console.log("获取用户数据");
    callback(null, { userId: 1 });
  }, 1000);
}

function getOrderData(userId, callback) {
  setTimeout(() => {
    console.log("获取订单数据");
    callback(null, { orderId: 101 });
  }, 1000);
}

function getProductData(orderId, callback) {
  setTimeout(() => {
    console.log("获取商品详情");
    callback(null, { productId: 1001, productName: "Laptop" });
  }, 1000);
}

function displayResult(product) {
  console.log("显示结果: ", product);
}

getUserData((error, user) => {
  if (error) {
    console.error("获取用户数据出错: ", error);
  } else {
    getOrderData(user.userId, (error, order) => {
      if (error) {
        console.error("获取订单数据出错: ", error);
      } else {
        getProductData(order.orderId, (error, product) => {
          if (error) {
            console.error("获取商品详情出错: ", error);
          } else {
            displayResult(product);
          }
        });
      }
    });
  }
});

那promise 是什么呢

假设你在游戏中领取一个任务完成后才能获取奖励。这是一个异步操作,因为你需要完成任务才能获取对应的奖励。Promise在这里就像是你的一个承诺(promise),它有三种可能的状态:

  1. Pending(进行中):任务进行中。
  2. Fulfilled(已完成):任务完成获取奖励。
  3. Rejected(已失败):任务失败。

Promise 是一种用于处理异步操作的对象。它代表了一个在未来可能完成或者失败的操作,并且可以用来处理操作的结果。Promise有三种状态:Pending(进行中)、Fulfilled(已成功)和Rejected(已失败)。
创建一个promise,promise构造函数接受一个执行函数作为参数,该执行函数有两个参数:resolve和reject。这两个参数也是函数,用于在异步操作成功或失败时分别处理结果。

const promise = new Promise((resolve, reject) => {
  // 异步操作
  if (操作成功) {
    resolve(结果值);  // 操作成功,传递结果值
  } else {
    reject(错误原因); // 操作失败,传递错误原因
  }
});

function isInclude(item,list){
    return new Promise(
        (resolve,reject)=>{
            list.includes(item) ? resolve(true) : reject(false)
        }
    )
}

promise实例上的方法

这些方法都需要传递一个回调函数,回调函数中执行处理不同结果的逻辑。

  • .then(): 处理成功的结果。
promise.then(value => {
  // 处理成功的结果
});

这里的value接收到的就是调用resolve传递值。

  • .catch(): 处理错误。
promise.catch(error => {
  // 处理错误
});

这里的error接收到的就是调用reject传递的错误信息。

  • .finally(): 无论成功或失败都会执行。
promise.finally(() => {
  // 最终执行的代码
});

finally的回调没有参数。 finally一般是在收尾的任务中,比如在页面打开时请求对应的数据进行页面渲染,在数据请求的过程中页面会有一个loading状态(页面加载一直转圈圈) 736c88b1bbfed3528ec3b2900b75de5a.gif 在这个情况下不管请求成功还是失败都应该取消这个状态,这个时候就需要finally收尾。

promise链式调用

链式调用就是将promise的方法调用多次,上一次方法的执行结果作为下一次方法的入参。

promise
  .then(value => {
    // 处理成功的结果
    return nextValue;
  })
  .then(nextValue => {
    // 处理下一个结果
  })
  .catch(error => {
    // 处理错误
  });

image.png image.png

promise
  .then(value => {
    // 处理成功的结果
    return nextValue;
  })
  .catch(error => {
    // 处理错误
  });

const myPromise = new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
        resolve("Success");
    }, 1000);
});

myPromise
    .then((result) => {
        console.log(result); // 输出: Success
        // 在 then 方法中抛出一个错误
        throw new Error("Something went wrong!");
    })
    .catch((error) => {
        // 捕获并处理错误
        console.error(error.message); // 输出: Something went wrong!
    });

const myPromise = new Promise((resolve, reject) => {
    // 模拟异步操作
    setTimeout(() => {
        reject(new Error("Initial error"));
    }, 1000);
});

myPromise
    .then((result) => {
        console.log("This will not be called");
        return result;
    })
    .catch((error) => {
        console.error("Caught an error:", error.message); // 输出: Initial error
        // 返回一个新的值,继续后续的 then 方法
        return "Recovered from error";
    })
    .then((result) => {
        console.log("Continuing with:", result); // 输出: Recovered from error
    })
    .catch((error) => {
        // 捕获任何在前面的 then 方法中抛出的错误
        console.error("Caught another error:", error.message);
    });

1、每一个then方法还是catch方法return的值都会被包裹为一个promise对象 2、上一个方法返回的值会作为下一个方法的参数 3、then方法可以通过throw抛出异常给catch处理,catch可以继续return将返回的值给then进行处理 4、每一个catch方法只会捕获自己前面发生的错误,捕获后面的catch并不会继续捕获。

为什么promise可以实现异步

Promise 并不直接实现异步操作,而是用于管理和协调异步操作的结果。JavaScript 本身提供了异步操作的机制,例如通过事件循环(Event Loop)和任务队列(Task Queue)来实现异步行为。Promise 是一种抽象,提供了一种更简洁和可读的方式来处理这些异步操作。

JavaScript 的异步机制

JavaScript 是单线程的,这意味着它一次只能执行一个任务。但是,为了避免在等待耗时操作(如网络请求、文件读取)时阻塞主线程,JavaScript 引入了事件循环和任务队列来管理异步任务。

  • 事件循环(Event Loop):事件循环不断检查调用栈(Call Stack)和任务队列(Task Queue)。如果调用栈为空,它会从任务队列中取出一个任务并将其推入调用栈执行。
  • 任务队列(Task Queue):异步操作(如setTimeout、网络请求)的回调函数会被放入任务队列中,等待事件循环将它们推入调用栈执行。

promise的运用

Promise在JavaScript中被广泛用于处理各种异步操作,特别是在处理网络请求、文件操作、定时器等场景中。下面我们通过几个实际应用的案例来展示Promise的运用。
在之前我们举了一个关于回调地域的例子,当我们使用promise来实现网络请求是什么样子呢 使用Promise可以避免回调地狱,使代码更加清晰和易于维护:

function getUserData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("获取用户数据");
      resolve({ userId: 1 });
    }, 1000);
  });
}

function getOrderData(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("获取订单数据");
      resolve({ orderId: 101 });
    }, 1000);
  });
}

function getProductData(orderId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("获取商品详情");
      resolve({ productId: 1001, productName: "Laptop" });
    }, 1000);
  });
}

function displayResult(product) {
  console.log("显示结果: ", product);
}

getUserData()
  .then(user => {
    return getOrderData(user.userId);
  })
  .then(order => {
    return getProductData(order.orderId);
  })
  .then(product => {
    displayResult(product);
  })
  .catch(error => {
    console.error("出错了: ", error);
  });

案例1:处理网络请求

假设我们需要从一个API获取用户信息,然后根据用户信息获取该用户的订单信息,最后获取每个订单的详细信息。

// 模拟一个异步请求:获取用户信息
function fetchUser() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("用户信息获取成功");
      resolve({ userId: 1, userName: "John Doe" });
    }, 1000);
  });
}

// 模拟一个异步请求:获取订单信息
function fetchOrders(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("订单信息获取成功");
      resolve([{ orderId: 101 }, { orderId: 102 }]);
    }, 1000);
  });
}

// 模拟一个异步请求:获取订单详情
function fetchOrderDetails(orderId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`订单${orderId}详情获取成功`);
      resolve({ orderId: orderId, product: "Laptop", price: 1000 });
    }, 1000);
  });
}

// 使用Promise处理这些异步请求
fetchUser()
  .then(user => {
    console.log("用户信息: ", user);
    return fetchOrders(user.userId);
  })
  .then(orders => {
    console.log("订单信息: ", orders);
    return Promise.all(orders.map(order => fetchOrderDetails(order.orderId)));
  })
  .then(orderDetails => {
    console.log("所有订单详情: ", orderDetails);
  })
  .catch(error => {
    console.error("出错了: ", error);
  });

案例2:顺序执行多个异步操作

有时候我们需要按顺序执行多个异步操作,并且前一个操作的结果会影响后一个操作的输入。我们可以通过Promise链式调用来实现这一点。

// 模拟一个异步操作:初始化应用
function initializeApp() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("应用初始化成功");
      resolve("初始化数据");
    }, 1000);
  });
}

// 模拟一个异步操作:用户登录
function userLogin(initData) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("用户登录成功");
      resolve({ userId: 1, token: "abc123" });
    }, 1000);
  });
}

// 模拟一个异步操作:获取用户配置
function fetchUserSettings(user) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("用户配置获取成功");
      resolve({ theme: "dark", language: "en" });
    }, 1000);
  });
}

// 使用Promise链式调用按顺序执行这些异步操作
initializeApp()
  .then(initData => {
    return userLogin(initData);
  })
  .then(user => {
    return fetchUserSettings(user);
  })
  .then(settings => {
    console.log("最终用户配置: ", settings);
  })
  .catch(error => {
    console.error("出错了: ", error);
  });

Promise.all

Promise.all 是JavaScript中的一个静态方法,用于处理多个Promise对象。当所有的Promise都成功时,Promise.all 返回一个新的Promise,其状态为Fulfilled,并携带一个包含所有Promise成功结果的数组;如果任何一个Promise失败,Promise.all 立即返回一个Rejected的Promise,并携带第一个失败的Promise的错误信息。

使用场景

Promise.all 通常用于需要并行执行多个异步操作,并且所有操作都完成后再进行下一步处理的情况。例如,从多个API获取数据(并发请求),读取多个文件,或执行多个独立的异步任务。

语法

Promise.all(iterable);
  • iterable 是一个可迭代对象(通常是一个数组),包含多个Promise对象。

示例

假设我们需要同时获取天气信息、新闻信息和股票信息,并在所有数据都获取完成后进行处理。

// 模拟异步操作:获取天气信息
function fetchWeather() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ weather: "Sunny", temperature: 30 });
    }, 1000);
  });
}

// 模拟异步操作:获取新闻信息
function fetchNews() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(["新闻1", "新闻2", "新闻3"]);
    }, 2000);
  });
}

// 模拟异步操作:获取股票信息
function fetchStocks() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ stock: "AAPL", price: 150 });
    }, 1500);
  });
}

// 使用Promise.all并行处理这些异步请求
Promise.all([fetchWeather(), fetchNews(), fetchStocks()])
  .then((results) => {
    const [weather, news, stocks] = results;
    console.log("天气信息: ", weather);
    console.log("新闻信息: ", news);
    console.log("股票信息: ", stocks);
  })
  .catch((error) => {
    console.error("出错了: ", error);
  });