【前端基础】Promise 详解

news2025/7/17 4:01:16

文章目录

    • 什么是 Promise?
    • 为什么要使用 Promise?
    • 创建 Promise
    • 消费 Promise (使用 Promise)
      • 1. `.then(onFulfilled, onRejected)`
      • 2. `.catch(onRejected)`
      • 3. `.finally(onFinally)`
    • Promise 链 (Promise Chaining)
    • Promise 的静态方法
      • 1. `Promise.resolve(value)`
      • 2. `Promise.reject(reason)`
      • 3. `Promise.all(iterable)`
      • 4. `Promise.allSettled(iterable)` (ES2020)
      • 5. `Promise.race(iterable)`
      • 6. `Promise.any(iterable)` (ES2021)
    • Promise 中的错误处理
    • Async/Await (基于 Promise 的语法糖)
    • 常见陷阱和最佳实践

在这里插入图片描述

Promise 是现代 JavaScript 中处理异步操作的核心机制,它极大地改善了回调函数(callback)带来的“回调地狱”问题,使异步代码更加清晰、易于管理和维护。

什么是 Promise?

Promise 对象用于表示一个异步操作的最终完成(或失败)及其结果值。简单来说,Promise 是一个承诺:它承诺在未来的某个时刻会给你一个结果,这个结果要么是成功的(fulfilled/resolved),要么是失败的(rejected)。

一个 Promise 对象必然处于以下三种状态之一:

  1. Pending (进行中):初始状态,既没有被兑现,也没有被拒绝。异步操作正在进行中。
  2. Fulfilled (已兑现/已成功):意味着异步操作成功完成。Promise 有一个与之关联的值(称为 resolved value)。
  3. Rejected (已拒绝/已失败):意味着异步操作失败。Promise 有一个与之关联的原因(通常是一个 Error 对象,称为 rejection reason)。

一旦 Promise 的状态从 pending 变为 fulfilled 或 rejected,它的状态和结果就固定不变了,不能再次改变。这种状态转变是单向的。我们称一个 Promise 为 settled (已敲定),如果它不是 pending 状态(即已兑现或已拒绝)。

为什么要使用 Promise?

  1. 告别回调地狱 (Callback Hell)
    当多个异步操作相互依赖时,使用传统回调函数很容易形成层层嵌套的结构,代码难以阅读和维护。Promise 通过链式调用 .then() 来扁平化这种结构。

    // 回调地狱示例
    asyncOperation1(data1, (err1, result1) => {
      if (err1) { /* ... */ }
      asyncOperation2(result1, (err2, result2) => {
        if (err2) { /* ... */ }
        asyncOperation3(result2, (err3, result3) => {
          // ...
        });
      });
    });
    
    // 使用 Promise
    asyncOperation1(data1)
      .then(result1 => asyncOperation2(result1))
      .then(result2 => asyncOperation3(result2))
      .then(result3 => { /* ... */ })
      .catch(error => { /* 处理任何步骤中的错误 */ });
    
  2. 更优雅的错误处理
    Promise 提供了 .catch() 方法,可以集中处理链中任何一个环节发生的错误,而不需要在每个回调中单独处理。

  3. 更好的代码可读性和可维护性
    链式调用使得异步操作的流程更加清晰,代码结构更接近同步代码的思维方式。

  4. 组合异步操作
    Promise 提供了一些静态方法(如 Promise.all(), Promise.race())来组合多个 Promise,实现更复杂的异步逻辑。

创建 Promise

可以通过 new Promise(executor) 构造函数来创建一个 Promise 对象。executor 是一个函数,它接收两个参数:resolvereject。这两个参数本身也是函数。

  • executor 函数会立即同步执行
  • executor 函数内部,通常会执行一些异步操作。
  • 当异步操作成功时,调用 resolve(value),将 Promise 的状态变为 fulfilled,并将 value 作为其结果。
  • 当异步操作失败时,调用 reject(reason),将 Promise 的状态变为 rejected,并将 reason (通常是一个 Error 对象) 作为其原因。
const myPromise = new Promise((resolve, reject) => {
  // 模拟一个异步操作
  console.log("Promise executor 开始执行"); // 这行会立即打印
  setTimeout(() => {
    const success = Math.random() > 0.5; // 随机成功或失败
    if (success) {
      resolve("操作成功!数据来了!"); // 成功时调用 resolve
    } else {
      reject(new Error("操作失败了,呜呜呜...")); // 失败时调用 reject
    }
  }, 1000);
});

console.log("Promise 已创建"); // 这行也会在 executor 内的 setTimeout 之前打印

消费 Promise (使用 Promise)

创建了 Promise之后,我们需要使用它的结果。这主要通过以下几个方法:

1. .then(onFulfilled, onRejected)

.then() 方法接收两个可选的函数作为参数:

  • onFulfilled:当 Promise 状态变为 fulfilled (成功) 时被调用,接收 Promise 的解决值作为参数。
  • onRejected:当 Promise 状态变为 rejected (失败) 时被调用,接收 Promise 的拒绝原因作为参数。

关键点.then() 方法返回一个新的 Promise。这使得链式调用成为可能。

myPromise
  .then(
    (value) => { // onFulfilled
      console.log("成功回调:", value);
      return "处理成功后的新值"; // 这个返回值会成为下一个 .then 的 onFulfilled 的参数
    },
    (error) => { // onRejected
      console.error("失败回调 (在 then 的第二个参数中):", error.message);
      // 如果在这里处理了错误,并且没有重新抛出或返回一个 rejected Promise,
      // 那么链条会继续走向 fulfilled 状态
      // throw error; // 如果想让链条继续保持 rejected 状态
      return "从错误中恢复的值";
    }
  )
  .then(
    (nextValue) => {
      console.log("下一个成功回调:", nextValue);
    },
    (nextError) => {
      console.error("下一个失败回调:", nextError.message);
    }
  );

2. .catch(onRejected)

.catch(onRejected) 方法仅仅是 .then(null, onRejected) 的语法糖。它用于捕获 Promise 链中任何未被处理的拒绝 (rejection)。通常放在 Promise 链的末尾,用于统一处理错误。

myPromise
  .then(value => {
    console.log("成功:", value);
    if (value.includes("数据")) {
      throw new Error("成功了,但我想抛个错试试!"); // 模拟在 then 中抛错
    }
    return value;
  })
  .catch(error => { // onRejected
    console.error("Catch 捕获到错误:", error.message);
    // 错误在这里被捕获和处理
    // 如果不重新 throw,链条会认为错误已处理,后续的 .then (如果有 onFulfilled) 可能会执行
  });

3. .finally(onFinally)

.finally(onFinally) 方法接收一个回调函数 onFinally,无论 Promise 是 fulfilled 还是 rejected,这个回调都会被执行。

  • onFinally 回调不接收任何参数。
  • 它通常用于执行一些清理工作,比如隐藏加载指示器、关闭数据库连接等。
  • .finally() 也会返回一个新的 Promise,它会采用原始 Promise 的状态和值/原因 (除非 onFinally 抛出错误或返回一个被拒绝的 Promise)。
myPromise
  .then(value => console.log("Finally 示例 - 成功:", value))
  .catch(error => console.error("Finally 示例 - 失败:", error.message))
  .finally(() => {
    console.log("Finally: 操作已完成,无论成功或失败都会执行我!");
    // 通常不在这里改变 Promise 的最终结果
  });

Promise 链 (Promise Chaining)

由于 .then(), .catch(), 和 .finally() 都返回新的 Promise 对象,我们可以将它们串联起来,形成一个 Promise 链。

  • 传递值:前一个 .thenonFulfilled 回调的返回值会作为下一个 .thenonFulfilled 回调的参数。
  • 异步操作的串行化:如果 .then 的回调函数返回一个新的 Promise,那么链条会等待这个新的 Promise 完成后,才会继续执行后续的 .then
function step1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("步骤1完成");
      resolve("来自步骤1的结果");
    }, 500);
  });
}

function step2(dataFromStep1) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("步骤2完成,接收到:", dataFromStep1);
      resolve("来自步骤2的结果");
    }, 500);
  });
}

function step3(dataFromStep2) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("步骤3完成,接收到:", dataFromStep2);
      resolve("最终结果");
    }, 500);
  });
}

step1()
  .then(result1 => step2(result1)) // step2 返回一个 Promise
  .then(result2 => step3(result2)) // step3 返回一个 Promise
  .then(finalResult => {
    console.log("所有步骤完成:", finalResult);
  })
  .catch(error => {
    console.error("链条中发生错误:", error);
  });

Promise 的静态方法

Promise 对象本身也提供了一些有用的静态方法:

1. Promise.resolve(value)

返回一个使用给定值解决的 Promise 对象。

  • 如果 value 本身是一个 Promise,则返回这个 Promise。
  • 如果 value 是一个 thenable 对象(即拥有 .then 方法的对象),Promise.resolve 会尝试展开这个 thenable 对象,并采用其最终状态。
  • 否则,返回的 Promise 将会以 value 值完成。
Promise.resolve("立即成功").then(v => console.log(v)); // 输出: 立即成功

const p = new Promise(r => setTimeout(() => r("延迟成功"), 100));
Promise.resolve(p).then(v => console.log(v)); // 等待 p 完成后输出: 延迟成功

2. Promise.reject(reason)

返回一个以给定原因拒绝的 Promise 对象。

Promise.reject(new Error("立即失败")).catch(e => console.error(e.message)); // 输出: 立即失败

3. Promise.all(iterable)

接收一个 Promise 对象的可迭代对象 (例如数组) 作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 只有在可迭代对象中所有的 Promise 都成功 (fulfilled) 时才会成功。其成功的值是一个数组,包含了所有输入 Promise 的成功值,顺序与输入 Promise 在可迭代对象中的顺序一致。
  • 如果可迭代对象中的任何一个 Promise 失败 (rejected),那么 Promise.all() 返回的 Promise 会立即失败,并以第一个失败的 Promise 的原因为准。
const promise1 = Promise.resolve(3);
const promise2 = 42; // 会被 Promise.resolve(42) 包装
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log("Promise.all 成功:", values); // 输出: [3, 42, "foo"]
}).catch(error => {
  console.error("Promise.all 失败:", error);
});

const promiseWithError = Promise.reject('错误了');
Promise.all([promise1, promiseWithError, promise3]).then((values) => {
  // 这段不会执行
}).catch(error => {
  console.error("Promise.all 因为一个错误而失败:", error); // 输出: 错误了
});

4. Promise.allSettled(iterable) (ES2020)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中所有的 Promise 都已敲定 (settled) 后(无论是 fulfilled 还是 rejected)才会完成。
  • 其成功的值是一个对象数组,每个对象描述了对应 Promise 的结果,格式为:
    • 成功:{ status: 'fulfilled', value: resolvedValue }
    • 失败:{ status: 'rejected', reason: rejectionReason }

这对于你希望等待多个异步操作完成,但不在乎它们是否都成功的情况非常有用。

const pSuccess = Promise.resolve("成功的值");
const pFailure = Promise.reject("失败的原因");
const pPending = new Promise(r => setTimeout(() => r("稍后成功"), 200));

Promise.allSettled([pSuccess, pFailure, pPending]).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log(`AllSettled - Fulfilled: ${result.value}`);
    } else {
      console.error(`AllSettled - Rejected: ${result.reason}`);
    }
  });
  // 输出:
  // AllSettled - Fulfilled: 成功的值
  // AllSettled - Rejected: 失败的原因
  // (等待200ms后) AllSettled - Fulfilled: 稍后成功
});

5. Promise.race(iterable)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中的任何一个 Promise 最先敲定 (settled) 时立即敲定,并采用第一个敲定的 Promise 的状态和结果(无论是 fulfilled 还是 rejected)。
const pFast = new Promise((resolve) => setTimeout(() => resolve('快速'), 50));
const pSlow = new Promise((resolve) => setTimeout(() => resolve('慢速'), 200));
const pReject = new Promise((resolve, reject) => setTimeout(() => reject('立即拒绝'), 10));

Promise.race([pFast, pSlow])
  .then(value => console.log("Race 胜出 (fast/slow):", value)) // 输出: 快速
  .catch(error => console.error("Race 失败 (fast/slow):", error));

Promise.race([pFast, pReject])
  .then(value => console.log("Race 胜出 (fast/reject):", value))
  .catch(error => console.error("Race 失败 (fast/reject):", error)); // 输出: 立即拒绝

6. Promise.any(iterable) (ES2021)

接收一个 Promise 对象的可迭代对象作为参数。

  • 返回一个新的 Promise。
  • 这个新的 Promise 会在可迭代对象中的任何一个 Promise 最先成功 (fulfilled) 时立即成功,并以第一个成功的 Promise 的值为准。
  • 如果可迭代对象中的所有 Promise 都失败 (rejected),那么 Promise.any() 返回的 Promise 会失败,并以一个 AggregateError 对象为原因。AggregateError 对象有一个 errors 属性,它是一个包含了所有拒绝原因的数组。
const pErr1 = Promise.reject('第一个错误');
const pErr2 = Promise.reject('第二个错误');
const pSucc1 = new Promise(r => setTimeout(() => r('第一个成功'), 50));
const pSucc2 = new Promise(r => setTimeout(() => r('第二个成功'), 10));

Promise.any([pErr1, pSucc1, pSucc2])
  .then(value => console.log("Any 成功:", value)) // 输出: 第二个成功 (因为它最先 fulfilled)
  .catch(error => console.error("Any 失败:", error));

Promise.any([pErr1, pErr2])
  .then(value => console.log("Any 成功 (全失败场景):", value)) // 不会执行
  .catch(error => {
    console.error("Any 失败 (全失败场景):", error.name); // AggregateError
    console.error(error.errors); // ['第一个错误', '第二个错误']
  });

Promise 中的错误处理

正确处理 Promise 中的错误至关重要。

  1. 使用 .catch()
    最佳实践是在 Promise 链的末尾添加一个 .catch() 来捕获所有未处理的拒绝。

  2. .then() 的第二个参数
    虽然 .then(onFulfilled, onRejected) 可以处理错误,但它不如 .catch() 清晰,且如果 onFulfilled 抛出错误,该错误不会被同一个 .thenonRejected 捕获,而是会传递给链中的下一个 .catch()onRejected

  3. Executor 中的同步错误
    如果在 new Promise(executor)executor 函数中抛出同步错误,这个错误会被隐式捕获,并导致 Promise 立即变为 rejected 状态。

    new Promise((resolve, reject) => {
      throw new Error("Executor 中的同步错误");
    }).catch(err => console.error(err.message)); // 输出: Executor 中的同步错误
    
  4. 未捕获的 Promise 拒绝 (Unhandled Rejection)
    如果一个 Promise 被拒绝,并且链上没有任何 .catch()onRejected 处理它,浏览器通常会在控制台报告一个 “Uncaught (in promise)” 错误。Node.js 环境中,这可能会导致应用进程终止(取决于版本和配置)。所以,务必处理所有可能的拒绝

Async/Await (基于 Promise 的语法糖)

ES2017 引入了 asyncawait 关键字,它们是构建在 Promise 之上的语法糖,使得异步代码的写法更像同步代码,从而更加直观。

  • async function:声明一个异步函数。异步函数会自动返回一个 Promise。如果函数内部返回一个非 Promise 值,这个值会被 Promise.resolve() 包装。如果函数内部抛出错误,返回的 Promise 会被 reject。
  • await:只能在 async function 内部使用。它会暂停异步函数的执行,等待 await 后面的 Promise 完成(resolved 或 rejected)。
    • 如果 Promise resolved,await 表达式的值就是 Promise 的解决值。
    • 如果 Promise rejected,await 会抛出这个拒绝的原因(通常是一个 Error 对象),这可以被 async function 内部的 try...catch 语句捕获。
function fetchData(shouldFail = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (shouldFail) {
        reject(new Error("数据获取失败 (async/await)"));
      } else {
        resolve("数据已获取 (async/await)");
      }
    }, 1000);
  });
}

async function processData() {
  console.log("开始处理数据...");
  try {
    const data1 = await fetchData(); // 等待 fetchData() 完成
    console.log("第一份数据:", data1);

    const data2 = await fetchData(true); // 尝试获取会失败的数据
    console.log("第二份数据:", data2); // 这行不会执行

  } catch (error) {
    console.error("Async/await 捕获到错误:", error.message);
  } finally {
    console.log("数据处理流程结束 (async/await)");
  }
}

processData();
// 输出顺序:
// 开始处理数据...
// (等待1秒)
// 第一份数据: 数据已获取 (async/await)
// (等待1秒)
// Async/await 捕获到错误: 数据获取失败 (async/await)
// 数据处理流程结束 (async/await)

async/await 极大地简化了异步流程控制,尤其是当涉及到多个 последовательных (sequential) 异步操作时。

常见陷阱和最佳实践

  1. 忘记 return:在 .then() 回调中,如果进行了异步操作并创建了新的 Promise,或者想将值传递给下一个 .then(),确保 return 这个 Promise 或值。否则,链条可能会意外地提前继续,或者下一个 .then() 接收到 undefined

  2. “吞没”错误:在 .catch()onRejected 中,如果没有显式地重新抛出错误 (throw error;) 或返回一个被拒绝的 Promise,那么 Promise 链会认为错误已经被处理,并可能转为 fulfilled 状态。

  3. 不必要的 Promise 包装:避免所谓的 “Promise constructor anti-pattern”,即在 new Promise 的 executor 中包装已经返回 Promise 的函数。

    // 反模式
    function getUserDataWrapper(userId) {
      return new Promise((resolve, reject) => { // 不必要的 Promise 包装
        fetch(`/api/users/${userId}`) // fetch 本身返回 Promise
          .then(response => response.json())
          .then(data => resolve(data))
          .catch(error => reject(error));
      });
    }
    
    // 更好的方式
    function getUserData(userId) {
      return fetch(`/api/users/${userId}`)
        .then(response => {
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return response.json();
        });
    }
    
  4. 总是添加 .catch():对于任何 Promise 链,都应该在末尾添加一个 .catch() 来处理潜在的未捕获错误。

  5. Promise.all vs Promise.allSettled:根据你的需求选择。如果任何一个失败都应该导致整个操作失败,使用 Promise.all。如果希望所有操作都尝试完成,并分别处理它们的结果,使用 Promise.allSettled

  6. 理解微任务队列:Promise 的 .then, .catch, .finally 的回调会作为微任务 (microtask) 被放入微任务队列中,它们会在当前宏任务 (macrotask) 执行完毕后、下一个宏任务开始前立即执行。这对于理解 Promise 的执行时机非常重要。

Promise 是 JavaScript 异步编程的基石,深刻理解它对于编写健壮、可维护的现代 JavaScript 应用至关重要。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2386867.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高性能管线式HTTP请求

高性能管线式HTTP请求:原理、实现与实践 目录 高性能管线式HTTP请求:原理、实现与实践 1. HTTP管线化的原理与优势 1.1 HTTP管线化的基本概念 关键特性: 1.2 管线化的优势 1.3 管线化的挑战 2. 高性能管线式HTTP请求的实现方案 2.1 技术选型与工具 2.2 Java实现:…

【CSS】九宫格布局

CSS Grid布局&#xff08;推荐&#xff09; 实现代码&#xff1a; <!doctype html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0"…

Python用Transformer、Prophet、RNN、LSTM、SARIMAX时间序列预测分析用电量、销售、交通事故数据

原文链接&#xff1a; tecdat.cn/?p42219 在数据驱动决策的时代&#xff0c;时间序列预测作为揭示数据时序规律的核心技术&#xff0c;已成为各行业解决预测需求的关键工具。从能源消耗趋势分析到公共安全事件预测&#xff0c;不同领域的数据特征对预测模型的适应性提出了差异…

java基础(面向对象进阶高级)泛型(API一)

认识泛型 泛型就等于一个标签(比如男厕所和女厕) 泛型类 只能加字符串&#xff1a; 把别人写好的东西&#xff0c;自己封装。 泛型接口 泛型方法、泛型通配符、上下限 怎么解决下面的问题&#xff1f; API object类 toString: equals: objects类 包装类 为什么上面的Integer爆红…

学习心得(17--18)Flask表单

一. 认识表单&#xff1a;定义表单类 password2中末端的EqualTo&#xff08;password&#xff09;是将密码2与密码1进行验证&#xff0c;看是否相同 二.使用表单&#xff1a; 运行 如果遇到这个报错&#xff0c;就在该页面去添加 下面是举例&#xff1a; 这就是在前端的展示效…

微信小程序进阶第2篇__事件类型_冒泡_非冒泡

在小程序中&#xff0c; 事件分为两种类型&#xff1a; 冒泡事件&#xff0c; 当一个组件上的事件被触发后&#xff0c;该事件会向父节点传递非冒泡事件&#xff0c; 当一个组件上的事件被触发后&#xff0c; 该事件不会向父节点传递。 一 冒泡事件 tap&#xff0c; touchst…

电机控制学习笔记

文章目录 前言一、电机二、编码器三、开环控制和闭环控制总结 前言 学习了解电机控制技术的一些原理和使用的方法。 一、电机 直流有刷电机 操作简单 使用H桥驱动直流有刷电机 直流有刷电机驱动板 电压检测 电流检测以及温度检测 直流无刷电机 使用方波或者正弦波进行换向…

企业网站架构部署与优化-Nginx性能调优与深度监控

目录 #1.1Nginx性能调优 1.1.1更改进程数与连接数 1.1.2静态缓存功能设置 1.1.3设置连接超时 1.1.4日志切割 1.1.5配置网页压缩 #2.1nginx的深度监控 2.1.1GoAccess简介 2.1.2nginx vts简介 1.1Nginx性能调优 1.1.1更改进程数与连接数 &#xff08;1&#xff09;进程数 进程数…

行列式的线性性质(仅限于单一行的加法拆分)

当然可以&#xff0c;以下是经过排版优化后的内容&#xff0c;保持了原始内容不变&#xff0c;仅调整了格式以提升可读性&#xff1a; 行列式的线性性质&#xff08;加法拆分&#xff09; 这个性质说的是&#xff1a;如果行列式的某一行&#xff08;或某一列&#xff09;的所有…

JAVA基础编程练习题--50道

一&#xff1a;循环结构 1.1 for循环 水鲜花数 &#xff08;1&#xff09;题目 &#xff08;2&#xff09;难点 如何获取三位数的个位数 如何计算一个数的立方 判断两数值是否相等 &#xff08;3&#xff09;代码 最大公约数 &#xff08;1&#xff09;题目 &#xff08;2&…

leetcode 93. Restore IP Addresses

题目描述 93. Restore IP Addresses 代码 回溯法 class Solution {vector<string> res; public:vector<string> restoreIpAddresses(string s) {string IP;int part 0;backtracking(s,0,IP,part);return res;}void backtracking(const string &s,int start…

pytest+allure+allure-pytest 报告输出遇到的问题汇总

文章目录 前言问题一&#xff1a;module allure has no attribute severity_level问题二&#xff1a;ERROR:file or directory not found: ‐vs问题三&#xff1a;生成的 html 报告是空的&#xff0c;明明有测试用例执行完成&#xff0c;但报告没有显示数据 前言 pytestallure…

【Opencv+Yolo】_Day1图像基本处理

目录 一、计算机中的视觉&#xff1a; 二、Opencv基本操作&#xff1a; 图片基础处理&#xff1a; 视频基本处理&#xff1a; 图像截取&#xff08;截取&#xff0c;合并&#xff0c;只保留一个元素&#xff09; 图像填充 数值计算 图像融合 阈值判断 图像平滑 图像腐…

从比分滚动到数据革命:体育数据如何重构我们的观赛体验?

当凌晨三点的欧冠决赛与闹钟冲突时&#xff0c;当世界杯小组赛因时差难以全程跟进时&#xff0c;当代体育迷早已不再依赖电视直播 —— 打开手机里的比分网&#xff0c;实时跳动的体育大数据正构建着全新的观赛宇宙。这些曾经被视为 "辅助工具" 的平台&#xff0c;如…

华为网路设备学习-23(路由器OSPF-LSA及特殊详解 二)

OSPF动态路由协议要求&#xff1a; 1.必须有一个骨干区域&#xff08;Area 0&#xff09;。有且仅有一个&#xff0c;而且连续不可分割。 2.所有非骨干区域&#xff08;Area 1-n&#xff09;必须和骨干区域&#xff08;Area 0&#xff09;直接相连&#xff0c;且所有区域之间…

VPet虚拟桌宠,一款桌宠软件,支持各种互动投喂等. 开源免费并且支持创意工坊

&#x1f4cc; 大家好&#xff0c;我是智界工具库&#xff0c;每天分享好用实用且智能的开源项目&#xff0c;以及在JAVA语言开发中遇到的问题&#xff0c;如果本篇文章对您有所帮助&#xff0c;请帮我点个小赞小收藏小关注吧&#xff0c;谢谢喲&#xff01;&#x1f618; 工具…

新书速览|ASP.NET MVC高效构建Web应用

《ASP.NET MVC高效构建Web应用》 本书内容 《ASP.NET MVC高效构建Web应用》以目前流行的ASP.NET MVC 5、HTML和Razor为主线&#xff0c;全面系统地介绍ASP.NET MVC Web应用开发的方法&#xff0c;配套提供实例源码、PPT课件与作者一对一QQ答疑服务。 《ASP.NET MVC高效构建Web…

MySQL 9.3 超详细下载安装教程(Windows版)附图文说明

MySQL 9.3 超详细下载安装教程&#xff08;Windows版&#xff09;附图文说明 &#x1f4a1; 本文适用于Windows 10/11系统&#xff0c;包含完整的安装流程、环境配置和疑难解答。建议收藏备用&#xff01; 一、下载MySQL 1. 访问官网 进入MySQL官方下载页面&#xff1a;http…

Linux之软件包管理器(CentOS系统) —— yum

目录 一、软件包管理器 1-1什么是软件包 1-2 Linux软件生态 Linux下载软件的过程(Ubuntu、Centos、other) 操作系统的好坏评估---生态问题 1. 应用软件生态 2. 硬件兼容性 3. 开发者社区与开源生态 4. 商业合作与盈利模式 5. 用户粘性与使用习惯 6. 安全与合规生态 …

webpack吐环境分析

需要解决的问题 扣取下来的webpack文件过大 解决思路 用ast将需要的代码扣下来 结果展示 实现步骤 第一步&#xff1a;我们得知道需要哪些模块 在入口处&#xff0c;增加模块记录 第二步&#xff0c;分析ast代码 通过分析发现,key 有三种值 分别为NumbericLiteral、StringLi…