面试题终极整理:手写 Promise.all / Promise.allSettled

第一部分:核心思路与架构

1.1 核心思路(一句话)

流式迭代输入(支持任意可迭代对象);
用"哨兵计数器"追踪所有异步任务的完成进度,用"索引占位"保证结果按输入顺序排列;
Promise.all 遇错即整体拒绝,Promise.allSettled 则等待全部落定后收集每个任务的状态对象。

1.2 解决方案流程图(文本版)

复制代码
                    promiseAll / promiseAllSettled(iterable)
                                      │
                                      ▼
                    ┌─────────────────────────────────┐
                    │ ① 获取迭代器                     │
                    │   iterable[Symbol.iterator]()    │
                    │   失败 → safeReject(TypeError)   │
                    └───────────────┬─────────────────┘
                                    ▼
                    ┌─────────────────────────────────┐
                    │ ② 初始化                         │
                    │   result   = []                  │
                    │   index    = 0                   │
                    │   remaining = 1  ← 哨兵占位令牌   │
                    │   done     = false               │
                    └───────────────┬─────────────────┘
                                    ▼
              ┌──────────────── while(!done) ────────────────┐
              │                                              │
              ▼                                              │
   ┌───────────────────────┐   抛错    ┌──────────────────┐  │
   │ step = iterator.next() │ ───────→ │ safeReject(err)  │  │
   └───────────┬───────────┘          │ ·iterator.return()│ │
               ▼                      │ ·reject(err)     │  │
   ┌───────────────────────┐   非对象   │ ·return 退出循环  │  │
   │ Object(step) !== step? │ ───────→ └──────────────────┘  │
   └───────────┬───────────┘                                 │
               ▼ 是合法对象                                   │
   ┌───────────────────────┐                                 │
   │ step.done === true ?   │──是──→ done = true              │
   └───────────┬───────────┘        settle()  ← 归还哨兵令牌   │
               │ 否                 break ────────────────────┘
               ▼
   ┌───────────────────────────────────────────┐
   │ remaining++                                │
   │ currentIndex = index++                     │
   │ Promise.resolve(step.value).then(          │
   │   成功: result[currentIndex] = 成功结果      │
   │   失败: all → reject(reason) 快速失败        │
   │        allSettled → 记录 {status:'rejected'}│
   │   settle() ← 所有分支都必须调用!             │
   │ )                                          │
   └───────────────────────────────────────────┘

               settle() 内部逻辑:
   ┌───────────────────────────────────────────┐
   │ remaining--                                │
   │ if (done && remaining === 0)               │
   │     resolve(result)   ← 唯一敲定出口        │
   └───────────────────────────────────────────┘

   计数器守恒方程:
   初始 1(哨兵)+ N(任务数)− 1(迭代结束归还)− N(任务全部落定)= 0 → resolve

1.3 哨兵计数器原理(架构图文本版)

复制代码
时间轴 ────────────────────────────────────────────────→

remaining:   1      2      3      2      1      0
             │      │      │      │      │      │
             │      │      │      │      │      └─ 最后一个任务 settle
             │      │      │      │      └─ 任务0 settle
             │      │      │      └─ 迭代结束,done=true,归还哨兵令牌
             │      │      └─ 注册任务1(remaining++)
             │      └─ 注册任务0(remaining++)
             └─ 初始哨兵 = 1,代表"迭代尚未完成"这件事本身

敲定条件 = done === true && remaining === 0
         = "迭代已结束" 且 "所有已注册任务都已落定"

为什么初始为 1 而不是 0?
  → 若初始为 0,空迭代器场景下 done 时的 settle() 会使 remaining = -1,
    需要额外写 if (length === 0) resolve([]) 特判分支。
  → 初始为 1 相当于"预先欠一个债",迭代结束时的 settle() 归还它,
    空/非空两条路径被统一到同一个归零判定上,消除所有特判分支。
    这是 core-js 源码的标准写法。

第二部分:主要矛盾与次要矛盾

2.1 主要矛盾(决定题目成败的核心问题)

优先级 矛盾 说明
矛盾一 异步完成时序 vs 结果顺序契约 任务完成顺序是乱的(先注册的可能后完成),但 Promise.all 规范要求结果数组必须与输入顺序一一对应。 解法:currentIndex 闭包捕获 + 索引占位写入
矛盾二 何时敲定(resolve 时机判定) 迭代是流式的,注册任务数事先未知;空数组还必须立即 resolve。 解法:哨兵计数器 remaining = 1,归零且 done 时敲定
矛盾三 计数完备性 计数器的每一次 ++ 必须有且仅有一次对应的 --。任何一个异步分支(成功/失败)漏掉 settle(),Promise 就永久挂起

2.2 次要矛盾(区分 9 分和 10 分的细节)

矛盾 说明
参数校验的严格程度 Array.from 会静默容忍不可迭代对象;规范做法是 Symbol.iterator 校验,非法输入必须 reject TypeError
迭代器资源清理 快速失败时应调用 iterator.return() 通知迭代器释放资源(如关闭数据库游标),依据 ECMAScript 规范 §7.4.6 IteratorClose
next() 返回值类型守卫 迭代器协议要求 next() 返回对象;Object(step) !== step 是规范级检查,手写实现常遗漏
next() 自身抛错 自定义迭代器的 next() 可能抛异常(如读取网络流的生成器),必须 try/catch
API 字段契约 allSettled 状态对象字段是 status (不是 state),值是 'fulfilled' / 'rejected'(不是 'success' / 'error'
表达式 vs 语句写法 .then(A, reject) 双参形式是表达式,可直接作为 Promise.resolve() 的链式调用;if (p.then) p.then(...) 是语句,需要独立分支,且对"伪 thenable"(有 then 属性但不是函数)判断不严
返回值契约 外层必须 return new Promise(...),漏 return 则函数返回 undefined

2.3 一个重要的"伪矛盾"澄清

Q:多个任务同时失败,会不会多次 reject 出问题?

A:不会。Promise 状态不可逆,第一次 reject 后,后续所有 reject/resolve 调用自动无效。因此"快速失败"无需任何额外的状态标记防护。同理,reject 之后迟到的 settle() 虽然会执行 remaining--,但因为 resolve 已被 Promise 状态机吞掉,无副作用。


第三部分:使用场景与边界场景

3.1 使用场景

场景 用哪个 原因
批量请求依赖全部成功(如页面初始化:用户信息 + 配置 + 权限必须齐全) Promise.all 任一失败整体失败,无意义的部分结果不消费
批量任务需要完整报告(如批量上传文件、批量校验表单、CI 并行测试) Promise.allSettled 单个失败不影响其他,需要知道"谁成功了谁失败了"
输入是生成器/惰性序列(如按页拉取数据的异步迭代器) 流式实现(本答案版本) Array.from 预加载版本会把惰性序列全部物化到内存,流式版本逐条处理,内存友好
需要子类化定制(库开发) 补充 Symbol.species 支持 MyPromise.all() 返回 MyPromise 而非原生 Promise

3.2 边界场景清单(写完必查)

边界 期望行为 验证要点
空数组 [] 立即 resolve([])(all)/ resolve([])(allSettled) 哨兵计数器独立归零路径
混合普通值与 Promise [1, Promise.resolve(2)] Promise.resolve 包装后统一处理 thenable 兼容
传入不可迭代对象 123 / null / {} reject TypeError Symbol.iterator 校验
全失败(allSettled) resolve,收集全部 {status:'rejected'} 计数完备性
成功失败混合(allSettled) resolve,按索引保序收集 保序 + 计数
next() 中途抛错 safeReject:清理迭代器 + reject IteratorClose
next() 返回非对象 reject TypeError 类型守卫
任务失败时其他任务仍在飞行 不取消其他任务,只是忽略其结果 Promise 无原生取消机制(这是 JS 的设计限制,不是 bug)
传入含 then 属性的普通对象 {then: 1} Promise.resolve 只对 then 是函数 的对象走 thenable 协议 规范行为,与手写实现无关但需知道

第四部分:完整示例代码(含详细注释)

4.1 手写 Promise.all(满分版)

js 复制代码
/**
 * 手写 Promise.all ------ 流式迭代 + 哨兵计数器版
 *
 * 语义:所有输入落定成功 → resolve 保序结果数组;任一失败 → 立即 reject 第一个错误
 *
 * @param {Iterable} iterable - 任意可迭代对象(数组、Set、生成器、Map.values() 等)
 * @returns {Promise<Array>} - 结果数组,顺序与输入严格一致
 */
function promiseAll(iterable) {
  // ★ 返回值契约:组合器本身必须返回 Promise(漏 return 是高频低级错误)
  return new Promise((resolve, reject) => {
    let iterator = null;

    /**
     * 迭代器层面出错时的统一出口(参数非法 / next() 抛错 / next() 返回非对象)
     * 注意:任务级别的失败不走这里,走下方 .then 的第二参数
     */
    const safeReject = (reason) => {
      // IteratorClose(ECMAScript 规范 §7.4.6):
      // 提前终止迭代时通知迭代器释放资源(如关闭文件句柄、数据库游标)
      // 若 return() 自身抛错,规范要求用新错误 reject ------
      // 由于我们身处 Promise executor 内,同步抛错会被自动转为 reject,恰好合规
      if (iterator && typeof iterator.return === 'function') {
        iterator.return();
      }
      reject(reason);
    };

    // ① 严格校验可迭代性:不可迭代对象必须 reject TypeError
    //    (不能用 Array.from 容错,那会把非法输入静默转为空处理)
    try {
      iterator = iterable[Symbol.iterator]();
    } catch (error) {
      safeReject(error);
      return; // 终止 executor 后续代码
    }

    let index = 0;          // 输入顺序计数器(决定结果的写入位置)
    let remaining = 1;      // ★ 哨兵占位:初始 1 代表"迭代尚未完成"这件事本身
                            //   作用:统一空/非空迭代器路径,消除 if(length===0) 特判
    let done = false;       // 迭代是否已结束
    const result = [];      // 结果数组(流式迭代下长度未知,只能动态增长)

    /**
     * 计数递减 + 敲定判定
     * 敲定条件:done === true(迭代结束)&& remaining === 0(所有债已还清)
     * 守恒方程:1(哨兵) + N(任务) - 1(迭代结束归还) - N(任务落定) = 0
     */
    const settle = () => {
      remaining--;
      if (done && remaining === 0) {
        resolve(result); // 唯一敲定出口;Promise 状态不可逆,重复调用无副作用
      }
    };

    // ② 流式迭代:不预加载,逐条消费,支持惰性/无限迭代器
    while (!done) {
      let step;
      try {
        step = iterator.next(); // next() 自身可能抛错,必须包裹
      } catch (error) {
        safeReject(error);
        return;
      }

      // 迭代器协议要求 next() 返回对象(规范级类型守卫)
      if (Object(step) !== step) {
        safeReject(new TypeError('Iterator result is not an object'));
        return;
      }

      // ③ 迭代结束:归还哨兵令牌
      if (step.done) {
        done = true;
        settle(); // 空迭代器场景:remaining 1→0,直接 resolve([])
        break;
      }

      // ④ 注册任务
      remaining++;                          // 借一个"债"
      const currentIndex = index++;         // ★ 闭包捕获当前索引 → 保序的核心

      // Promise.resolve 包装:统一处理普通值、原生 Promise、第三方 thenable
      // 表达式写法:直接作为 .then 的参数,比 if(p.then) 语句写法更紧凑、
      //           且能正确区分"then 是函数"与"then 是普通属性"
      Promise.resolve(step.value).then(
        (value) => {
          result[currentIndex] = value;     // 按输入索引写入,与完成时序无关
          settle();                         // 还一个"债"
        },
        reject // 快速失败:第一个错误直接 reject 整体;
               // 其余任务的迟到结果因 Promise 状态不可逆被自动忽略
      );
    }
  });
}

// ==================== 四条路径验证 ====================

// 路径1:全成功 + 普通值混合 + 保序验证
promiseAll([
  Promise.resolve('a'),
  'b',                                  // 普通值也要正确处理
  new Promise(r => setTimeout(() => r('c'), 100)) // 慢任务
]).then(console.log);
// 期望: ['a', 'b', 'c'](尽管 'c' 最后完成,仍排在索引 2)

// 路径2:全失败 → 快速失败
promiseAll([Promise.reject('e1'), Promise.reject('e2')])
  .catch(console.log);
// 期望: 'e1'(第一个到达的错误;Promise 状态不可逆,'e2' 被忽略)

// 路径3:混合(一个失败拖垮整体,即使其他成功)
promiseAll([Promise.resolve('ok'), Promise.reject('err')])
  .catch(console.log);
// 期望: 'err'

// 路径4:空数组 → 立即 resolve
promiseAll([]).then(console.log);
// 期望: []

// 路径5(进阶):非法输入
promiseAll(123).catch(e => console.log(e instanceof TypeError)); // true

4.2 手写 Promise.allSettled(满分版,含 .finally 防遗漏加固)

js 复制代码
/**
 * 手写 Promise.allSettled ------ 等待全部落定,永不因任务失败而 reject
 *
 * 语义:所有输入落定后 resolve 状态对象数组(保序);
 *      仅在"迭代器层面"出错时才 reject(参数非法 / next() 抛错)
 *
 * 状态对象契约(ECMAScript 规范,字段名和字符串字面量都必须精确):
 *   成功: { status: 'fulfilled', value: 结果值 }
 *   失败: { status: 'rejected',  reason: 错误原因 }
 *
 * @param {Iterable} iterable
 * @returns {Promise<Array<{status:string, value?:any, reason?:any}>>}
 */
function promiseAllSettled(iterable) {
  return new Promise((resolve, reject) => {
    let iterator = null;

    // 仅迭代器层面出错时调用;任务失败走下方 then 第二参数,不经过这里
    const safeReject = (reason) => {
      if (iterator && typeof iterator.return === 'function') {
        iterator.return(); // IteratorClose 资源清理
      }
      reject(reason);
    };

    try {
      iterator = iterable[Symbol.iterator]();
    } catch (error) {
      safeReject(error);
      return;
    }

    let index = 0;
    let remaining = 1;  // 哨兵占位(原理同 promiseAll)
    let done = false;
    const result = [];

    const settle = () => {
      remaining--;
      if (done && remaining === 0) {
        resolve(result);
      }
    };

    while (!done) {
      let step;
      try {
        step = iterator.next();
      } catch (error) {
        safeReject(error);
        return;
      }

      if (Object(step) !== step) {
        safeReject(new TypeError('Iterator result is not an object'));
        return;
      }

      if (step.done) {
        done = true;
        settle();
        break;
      }

      remaining++;
      const currentIndex = index++;

      // ★ 加固写法:成功/失败分支只负责写结果,计数统一交给 finally
      //   好处:从结构上杜绝"某个分支忘写 settle() 导致死锁"的可能
      Promise.resolve(step.value)
        .then(
          (value) => {
            // 字段名必须是 status(不是 state),值必须是 'fulfilled'
            result[currentIndex] = { status: 'fulfilled', value };
          },
          (reason) => {
            // 字段名必须是 status,值必须是 'rejected'
            result[currentIndex] = { status: 'rejected', reason };
          }
        )
        .finally(() => {
          settle(); // 无论成败都计数 → 计数器守恒不可能被打破
        });
    }
  });
}

// ==================== 四条路径验证 ====================

// 路径1:混合成功失败(保序 + 完整收集)
promiseAllSettled([
  Promise.resolve('ok'),
  Promise.reject('err'),
  42 // 普通值
]).then(console.log);
// 期望:
// [ { status: 'fulfilled', value: 'ok' },
//   { status: 'rejected',  reason: 'err' },
//   { status: 'fulfilled', value: 42 } ]

// 路径2:全失败
promiseAllSettled([Promise.reject('a'), Promise.reject('b')])
  .then(console.log);
// 期望: [{status:'rejected',reason:'a'}, {status:'rejected',reason:'b'}]
// 关键:整体 Promise 是 fulfilled 的!allSettled 永不因任务失败而 reject

// 路径3:全成功
promiseAllSettled([Promise.resolve(1), Promise.resolve(2)])
  .then(console.log);
// 期望: [{status:'fulfilled',value:1}, {status:'fulfilled',value:2}]

// 路径4:空数组
promiseAllSettled([]).then(console.log); // 期望: []

// 路径5:迭代器中途抛错(唯一的 reject 场景)
function* brokenGenerator() {
  yield Promise.resolve(1);
  throw new Error('generator exploded');
}
promiseAllSettled(brokenGenerator())
  .catch(e => console.log(e.message)); // 'generator exploded'

// 路径6:资源清理验证(快速失败时 iterator.return() 被调用)
const closableIterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i++ === 0) return { value: Promise.resolve(1), done: false };
        throw new Error('next() failed');
      },
      return() {
        console.log('iterator.return() called → 资源已清理');
        return { done: true };
      }
    };
  }
};
promiseAllSettled(closableIterable).catch(() => {});
// 期望控制台输出: 'iterator.return() called → 资源已清理'

4.3 简化版对照(面试首轮可先写,说明 trade-off 后再升级)

js 复制代码
/**
 * Promise.all 简化版 ------ Array.from 预加载
 * 优点:代码短,一眼看懂,适合面试首轮快速给出
 * 缺点:① Array.from 会静默容忍不可迭代对象(校验不严)
 *      ② 预加载整个序列到内存,无法处理惰性/无限迭代器
 * 面试策略:先写这版跑通语义,再主动说明局限,升级为流式版 → 展示思维层次
 */
function promiseAllSimple(iterable) {
  // Array.from 把可迭代对象物化为数组(同时获得确定的长度)
  const items = Array.from(iterable);
  const total = items.length;

  // 显式特判空数组(流式版靠哨兵计数器免除了这个分支)
  if (total === 0) return Promise.resolve([]);

  return new Promise((resolve, reject) => {
    const result = new Array(total); // 定长预分配(流式版做不到,因为长度未知)
    let count = 0;

    items.forEach((item, i) => {
      Promise.resolve(item).then(
        (value) => {
          result[i] = value;  // 索引写入 → 保序
          // 计数归零判定(注意:这里不需要 done 标志,
          // 因为 total 在迭代前就已知)
          if (++count === total) resolve(result);
        },
        reject // 快速失败
      );
    });
  });
}

第五部分:补充知识点(对话中未展开但面试会追问)

5.1 四个 Promise 组合器的语义对比

方法 敲定条件 结果 失败条件
Promise.all 全部 fulfilled 保序值数组 任一 rejected 立即整体 rejected
Promise.allSettled 全部落定 保序状态对象数组 {status, value/reason} 几乎永不(仅参数非法)
Promise.race 第一个落定 第一个落定者的结果(无论成败) 第一个落定者是 rejected
Promise.any 第一个 fulfilled 第一个成功值 全部 rejected → AggregateError

记忆口诀:all 是"与"(AND),any 是"或"(OR),race 是"竞速",allSettled 是"点名"。

5.2 为什么快速失败不需要"取消"其他任务?

JavaScript 的 Promise 没有原生取消机制Promise.all 快速失败后,其余在途任务继续执行到自然结束,只是结果被忽略(写入 result 的动作发生在已 reject 的闭包里,无副作用)。如需真正的取消,要在应用层配合 AbortController

js 复制代码
// AbortController 配合示例:真正取消在途 fetch
const controller = new AbortController();
Promise.all(
  urls.map(url => fetch(url, { signal: controller.signal }))
).catch(err => {
  controller.abort(); // 通知所有未完成的 fetch 中止
  throw err;
});

5.3 Symbol.species(子类化支持,库级加分项)

js 复制代码
// 原生 Promise.all 内部会用 this.constructor[Symbol.species] 决定
// 返回哪个构造器创建的 Promise,使子类调用 MyPromise.all() 得到 MyPromise。
// 手写版若要支持子类化,把 new Promise(...) 换成:
const C = this.constructor[Symbol.species] || Promise;
return new C((resolve, reject) => { /* ... */ });

5.4 TypeScript 签名(工程化加分项)

ts 复制代码
declare function promiseAll<T>(
  iterable: Iterable<T | PromiseLike<T>>
): Promise<Awaited<T>[]>;

declare function promiseAllSettled<T>(
  iterable: Iterable<T | PromiseLike<T>>
): Promise<PromiseSettledResult<Awaited<T>>[]>;
// PromiseSettledResult<T> 是 TypeScript 内置工具类型:
//   { status: 'fulfilled'; value: T } | { status: 'rejected'; reason: any }

第六部分:满分答案(终极浓缩版)

面试现场直接默写这一份。写完立刻 trace 四条路径:空 / 全成 / 全败 / 混合。

核心思路(一句话)

流式迭代 + 索引闭包保序 + 哨兵计数器敲定;all 遇错即拒(双参 then 表达式),allSettled 全收状态(finally 统一计数防死锁)。

架构图(一句话版)

复制代码
iterator.next() 流式消费 → Promise.resolve 统一包装 → currentIndex 保序写入
→ settle() 递减计数 → done && remaining===0 → resolve(result)
任何迭代器层错误 → safeReject(iterator.return() 清理 + reject)

满分代码:Promise.all

js 复制代码
function promiseAll(iterable) {
  return new Promise((resolve, reject) => {
    let iterator = null;
    const safeReject = (reason) => {
      if (iterator && typeof iterator.return === 'function') iterator.return();
      reject(reason);
    };

    try {
      iterator = iterable[Symbol.iterator](); // 严格校验可迭代性
    } catch (error) {
      return safeReject(error);
    }

    let index = 0;
    let remaining = 1; // 哨兵:代表"迭代未完成",统一空/非空路径
    let done = false;
    const result = [];

    const settle = () => {
      if (--remaining === 0 && done) resolve(result);
    };

    while (!done) {
      let step;
      try {
        step = iterator.next();
      } catch (error) {
        return safeReject(error);
      }
      if (Object(step) !== step) {
        return safeReject(new TypeError('Iterator result is not an object'));
      }
      if (step.done) {
        done = true;
        settle(); // 归还哨兵令牌;空迭代器在此直接 resolve([])
        break;
      }

      remaining++;
      const currentIndex = index++; // 闭包捕获索引 → 保序核心
      Promise.resolve(step.value).then(
        (value) => { result[currentIndex] = value; settle(); },
        reject // 快速失败;Promise 状态不可逆,无需额外防护
      );
    }
  });
}

满分代码:Promise.allSettled

js 复制代码
function promiseAllSettled(iterable) {
  return new Promise((resolve, reject) => {
    let iterator = null;
    const safeReject = (reason) => {
      if (iterator && typeof iterator.return === 'function') iterator.return();
      reject(reason);
    };

    try {
      iterator = iterable[Symbol.iterator]();
    } catch (error) {
      return safeReject(error);
    }

    let index = 0;
    let remaining = 1; // 哨兵
    let done = false;
    const result = [];

    const settle = () => {
      if (--remaining === 0 && done) resolve(result);
    };

    while (!done) {
      let step;
      try {
        step = iterator.next();
      } catch (error) {
        return safeReject(error);
      }
      if (Object(step) !== step) {
        return safeReject(new TypeError('Iterator result is not an object'));
      }
      if (step.done) {
        done = true;
        settle();
        break;
      }

      remaining++;
      const currentIndex = index++;
      Promise.resolve(step.value)
        .then(
          (value) => { result[currentIndex] = { status: 'fulfilled', value }; },
          (reason) => { result[currentIndex] = { status: 'rejected', reason }; }
        )
        .finally(settle); // ★ 计数统一收口,结构上杜绝漏 settle 死锁
    }
  });
}

满分口头阐述模板(30 秒)

"我用了流式迭代而非 Array.from,一是校验更严格------不可迭代对象会 reject TypeError,二是支持生成器等惰性序列、内存友好。计数上用了 core-js 同款的哨兵技巧:remaining 初始为 1 代表迭代未完成,每注册一个任务加一,迭代结束和任务落定各减一,归零且 done 时敲定,这样空数组不需要特判。保序靠闭包捕获 currentIndex。Promise.all 用双参 then 表达式快速失败,依赖 Promise 状态不可逆特性,不需要额外防护;allSettled 我把计数放到 finally 里统一收口,从结构上杜绝了某个分支漏调 settle 导致的死锁------这是这类实现最隐蔽的 bug。另外快速失败路径我调用了 iterator.return() 做资源清理,符合 IteratorClose 规范。"


最终结语:这道题的完整知识图谱 = 返回值契约 + 迭代器协议(校验/类型守卫/next 抛错/return 清理)+ 哨兵计数器 + 索引保序 + 计数完备性 + API 字段契约 + 状态不可逆特性。五轮迭代你已全部踩过并修复,剩下的就是把第七部分固化为肌肉记忆------下次直接默写满分版。

相关推荐
风骏时光牛马1 小时前
AI面试复盘:沉淀经验,找准求职短板
前端
心易行者1 小时前
用HTML在线运行搭后台管理系统:5个核心模块+0服务器,3天跑通完整业务
大数据·前端·网络·人工智能·python
单线程121381 小时前
从案例分析 Vue3 Tokenizer+Parser 源码五
前端·javascript·vue.js
神奇的程序员1 小时前
Apple Watch S12,送一台
前端
moonsims2 小时前
Voliro 无人机-Aerial Mobile Robot(空中移动机器人):把无人机从“飞过去拍摄”,升级成“飞过去并与目标物理接触、测量甚至操作”
前端·人工智能·安全·无人机·量子计算
晴天163 小时前
Chrome 控制台(DevTools)竟是用 HTML 写的?-Day41
前端·chrome·html
变与不变8063 小时前
Debug 调试与排错规范
前端·javascript
欢迎来到祖安!4 小时前
企业微信 API 接口能力介绍:支持消息收发、图片发送、表情互动、联系人管理等多场景应用
java·前端·数据仓库·spring cloud·企业微信
子非鱼a9 小时前
【WEB】multiSQL
前端