阅读视图

发现新文章,点击刷新页面。
🔲 ☆

Promise.try()和Promise.withResolvers()作用速览

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12048
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

本文介绍两个Promise相关的新特性。

一、Promise.try()的作用

之前我们运行一段代码,或者一个函数,想要捕获错误的时候,往往使用的是try...catch(),对吧。

但是try...catch()呢有个小问题,那就是如果里面有异步操作,如 setTimeout、Promise 内部,那么这个错误就捕获不了。

Promise.try()的作用之一就是统一同步与异步错误处理。

例如:

try {
  new Promise(resolve => resolve(callback());
} catch (e) {
  // 错误提示
}

如果这里的callback是异步的,那么上面的实现是无法捕获错误的。

但是下面的可以:

Promise.try(callback)
  .then(result => console.log(result))
  .catch(error => console.log(error))
  .finally(() => console.log("All settled."));

//zxx: 如果使用 async/await 语法,请不要使用 Promise.try,而应改用 try/catch/finally 块

更新于2026年1月26日

Promise.try()也不能捕获setTimeout内部的错误,除非在 setTimeout 内部返回 Promise

Promise.try() 只能捕获同步执行或返回 Promise 的异步错误,但 setTimeout 会创建一个新的执行上下文。

// ❌ 无法捕获
Promise.try(() => {
  setTimeout(() => {
    throw new Error('这个错误无法被捕获');
  }, 1000);
}).catch(err => {
  console.log('永远不会执行', err);
});

// setTimeout 的回调在 Promise 已经 resolve 之后才执行

具体见下表:

场景 Promise.try() 能否捕获
同步错误 ✅ 可以
返回的 Promise 错误 ✅ 可以
setTimeout 内部错误 ❌ 不可以
setInterval 内部错误 ❌ 不可以
事件回调内部错误 ❌ 不可以

二、Promise.try()的语法

语法使用示意如下:

Promise.try(func)
Promise.try(func, arg1)
Promise.try(func, arg1, arg2)
Promise.try(func, arg1, arg2, /* …, */ argN)

会返回一个 Promise,其状态可以是:

  • 已兑现的,如果 func 同步地返回一个值。
  • 已拒绝的,如果 func 同步地抛出一个错误。
  • 异步兑现或拒绝的,如果 func 返回一个 promise。

如果回调函数有参数,该怎么办?您可以通过以下两种方式之一来处理此问题:

// 创建了额外的闭包,但是也是可以运行的
Promise.try(() => callback(param1, param2));

// 不创建闭包,同样可以运行
Promise.try(callback, param1, param2);

更推荐使用后面的用法。

兼容性

目前所有现代浏览器都已经支持了,兼容性还是不错的,不支持的浏览器也可以引入polyfill进行兼容。

Promise.try()兼容性

三、Promise.withResolvers()的作用

Promise.withResolvers() 是 ECMAScript 2024 中新增的一个静态方法,其核心作用是将 Promise 的创建与其状态控制(resolve 和 reject)解耦,允许开发者同时获得一个新的 Promise 实例以及与其绑定的、用于控制其状态的函数。

使用示意:

function createControllablePromise() {
  // 返回 { promise, resolve, reject }
  return Promise.withResolvers();
}

const { promise, resolve, reject } = createControllablePromise();

// 2秒后手动 resolve
setTimeout(() => {
  resolve('成功了!');
}, 2000);

promise.then(result => {
  console.log(result); // 应该输出: 成功了!
});

对比案例

传统实现:

function withTimeout(asyncOperation, timeoutMs) {
  // 必须预先声明变量,用于在外部存储控制函数
  let resolveRef, rejectRef;

  // 创建控制超时的Promise
  const timeoutPromise = new Promise((resolve, reject) => {
    // 在构造函数内部,将内部的resolve和reject赋值给外部变量
    resolveRef = resolve;
    rejectRef = reject;

    // 设置超时定时器
    setTimeout(() => {
      reject(new Error(`操作超时,超过 ${timeoutMs}ms`));
    }, timeoutMs);
  });

  // 执行实际的异步操作
  asyncOperation()
    .then((result) => {
      // 异步操作成功,手动解决超时Promise
      resolveRef(result);
    })
    .catch((error) => {
      // 异步操作失败,手动拒绝超时Promise
      rejectRef(error);
    });

  // 返回这个受超时控制的Promise
  return timeoutPromise;
}

改为使用Promise.withResolvers()方法后:

function withTimeout(asyncOperation, timeoutMs) {
  // 一行代码同时获得Promise实例及其控制函数
  const { promise, resolve, reject } = Promise.withResolvers();

  // 设置超时定时器
  setTimeout(() => {
    reject(new Error(`操作超时,超过 ${timeoutMs}ms`));
  }, timeoutMs);

  // 执行实际的异步操作
  asyncOperation()
    .then((result) => {
      // 异步操作成功,解决Promise
      resolve(result);
    })
    .catch((error) => {
      // 异步操作失败,拒绝Promise
      reject(error);
    });

  // 返回Promise
  return promise;
}

可以看到Promise.withResolvers()的实现代码更加简洁,此API特别适用于事件监听、流处理、队列管理、超时控制等高级异步场景。

兼容性

Promise.withResolvers()方法的的兼容性要比Promise.try()更好一些,支持更早一些,如下截图所示。

兼容性

已经快要可以放心使用了。

四、结语说明

本文介绍的两个特性都属于语法层面增强的特性,通过提供更优雅的语法,显著提升了代码的可读性、可维护性。它代表了 JavaScript 异步编程向更简洁、更直观方向演进的重要一步。

实际上,在我看来,目前很多前端特性是过盛的。

每年前端领域的新特性没有100也有80,但是在生产环境使用的,寥寥无几。

等以后AI盛行之后,更回事如此,因为AI所使用的技术实现,一定是传统的,稳健的实现方式。

所谓的更高效更简洁,很难反应到真实生产环境中。

所以,目前来看,个体的学习还是不能停止的。

好啦,就这样吧。

感谢阅读,欢迎交流!

感谢

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12048

(本篇完)

🔲 ☆

Promise.try()和Promise.withResolvers()作用速览

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=12048
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

本文介绍两个Promise相关的新特性。

一、Promise.try()的作用

之前我们运行一段代码,或者一个函数,想要捕获错误的时候,往往使用的是try...catch(),对吧。

但是try...catch()呢有个小问题,那就是如果里面有异步操作,如 setTimeout、Promise 内部,那么这个错误就捕获不了。

Promise.try()的作用之一就是统一同步与异步错误处理。

例如:

try {
  new Promise(resolve => resolve(callback());
} catch (e) {
  // 错误提示
}

如果这里的callback是异步的,那么上面的实现是无法捕获错误的。

但是下面的可以:

Promise.try(callback)
  .then(result => console.log(result))
  .catch(error => console.log(error))
  .finally(() => console.log("All settled."));

//zxx: 如果使用 async/await 语法,请不要使用 Promise.try,而应改用 try/catch/finally 块

更新于2026年1月26日

Promise.try()也不能捕获setTimeout内部的错误,除非在 setTimeout 内部返回 Promise

Promise.try() 只能捕获同步执行或返回 Promise 的异步错误,但 setTimeout 会创建一个新的执行上下文。

// ❌ 无法捕获
Promise.try(() => {
  setTimeout(() => {
    throw new Error('这个错误无法被捕获');
  }, 1000);
}).catch(err => {
  console.log('永远不会执行', err);
});

// setTimeout 的回调在 Promise 已经 resolve 之后才执行

具体见下表:

场景 Promise.try() 能否捕获
同步错误 ✅ 可以
返回的 Promise 错误 ✅ 可以
setTimeout 内部错误 ❌ 不可以
setInterval 内部错误 ❌ 不可以
事件回调内部错误 ❌ 不可以

二、Promise.try()的语法

语法使用示意如下:

Promise.try(func)
Promise.try(func, arg1)
Promise.try(func, arg1, arg2)
Promise.try(func, arg1, arg2, /* …, */ argN)

会返回一个 Promise,其状态可以是:

  • 已兑现的,如果 func 同步地返回一个值。
  • 已拒绝的,如果 func 同步地抛出一个错误。
  • 异步兑现或拒绝的,如果 func 返回一个 promise。

如果回调函数有参数,该怎么办?您可以通过以下两种方式之一来处理此问题:

// 创建了额外的闭包,但是也是可以运行的
Promise.try(() => callback(param1, param2));

// 不创建闭包,同样可以运行
Promise.try(callback, param1, param2);

更推荐使用后面的用法。

兼容性

目前所有现代浏览器都已经支持了,兼容性还是不错的,不支持的浏览器也可以引入polyfill进行兼容。

Promise.try()兼容性

三、Promise.withResolvers()的作用

Promise.withResolvers() 是 ECMAScript 2024 中新增的一个静态方法,其核心作用是将 Promise 的创建与其状态控制(resolve 和 reject)解耦,允许开发者同时获得一个新的 Promise 实例以及与其绑定的、用于控制其状态的函数。

使用示意:

function createControllablePromise() {
  // 返回 { promise, resolve, reject }
  return Promise.withResolvers();
}

const { promise, resolve, reject } = createControllablePromise();

// 2秒后手动 resolve
setTimeout(() => {
  resolve('成功了!');
}, 2000);

promise.then(result => {
  console.log(result); // 应该输出: 成功了!
});

对比案例

传统实现:

function withTimeout(asyncOperation, timeoutMs) {
  // 必须预先声明变量,用于在外部存储控制函数
  let resolveRef, rejectRef;

  // 创建控制超时的Promise
  const timeoutPromise = new Promise((resolve, reject) => {
    // 在构造函数内部,将内部的resolve和reject赋值给外部变量
    resolveRef = resolve;
    rejectRef = reject;

    // 设置超时定时器
    setTimeout(() => {
      reject(new Error(`操作超时,超过 ${timeoutMs}ms`));
    }, timeoutMs);
  });

  // 执行实际的异步操作
  asyncOperation()
    .then((result) => {
      // 异步操作成功,手动解决超时Promise
      resolveRef(result);
    })
    .catch((error) => {
      // 异步操作失败,手动拒绝超时Promise
      rejectRef(error);
    });

  // 返回这个受超时控制的Promise
  return timeoutPromise;
}

改为使用Promise.withResolvers()方法后:

function withTimeout(asyncOperation, timeoutMs) {
  // 一行代码同时获得Promise实例及其控制函数
  const { promise, resolve, reject } = Promise.withResolvers();

  // 设置超时定时器
  setTimeout(() => {
    reject(new Error(`操作超时,超过 ${timeoutMs}ms`));
  }, timeoutMs);

  // 执行实际的异步操作
  asyncOperation()
    .then((result) => {
      // 异步操作成功,解决Promise
      resolve(result);
    })
    .catch((error) => {
      // 异步操作失败,拒绝Promise
      reject(error);
    });

  // 返回Promise
  return promise;
}

可以看到Promise.withResolvers()的实现代码更加简洁,此API特别适用于事件监听、流处理、队列管理、超时控制等高级异步场景。

兼容性

Promise.withResolvers()方法的的兼容性要比Promise.try()更好一些,支持更早一些,如下截图所示。

兼容性

已经快要可以放心使用了。

四、结语说明

本文介绍的两个特性都属于语法层面增强的特性,通过提供更优雅的语法,显著提升了代码的可读性、可维护性。它代表了 JavaScript 异步编程向更简洁、更直观方向演进的重要一步。

实际上,在我看来,目前很多前端特性是过盛的。

每年前端领域的新特性没有100也有80,但是在生产环境使用的,寥寥无几。

等以后AI盛行之后,更回事如此,因为AI所使用的技术实现,一定是传统的,稳健的实现方式。

所谓的更高效更简洁,很难反应到真实生产环境中。

所以,目前来看,个体的学习还是不能停止的。

好啦,就这样吧。

感谢阅读,欢迎交流!

感谢

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=12048

(本篇完)

🔲 ☆

醒醒,该使用CookieStore新建和管理cookie了

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11954
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、为什么要抛弃传统Cookie操作?

在前端开发的长河中,Cookie始终扮演着重要角色。

从用户身份识别到状态维持,它是浏览器与服务器之间轻量通信的核心载体。

但长期以来,我们操作Cookie的方式始终停留在通过document.cookie拼接字符串的“原始阶段”,不仅代码冗余易出错,还无法应对异步场景下的复杂需求。

比方说,我们要设置一个Cookie,需要先获取,然后再手动进行键值对+属性的字符串拼接,例如:

document.cookie = "name=zhangxinxu; max-age=3600; path=/"

这就很麻烦,属性顺序、符号格式稍有偏差就会导致失效。

宋玉摊手

然后,我们要获取Cookie的时候,也是先获取,再字符串split分隔,再去匹配,代码冗长,还不支持批量操作。

比方说,我想一次性删除多个符合条件的Cookie,需循环遍历逐个处理。

正是因为上面这些不足,CookieStore API才应运而生。

它将Cookie操作封装为标准化的异步方法,让Cookie管理变得简洁、可控。

二、CookieStore对象特性概览

CookieStore是浏览器提供的现代API,隶属于Window对象,用于以异步、面向对象的方式操作Cookie。

包含的方法名如下所示:

// 删除cookie
cookieStore.delete()

// 获取cookie
cookieStore.get()

// 获取所有cookie
cookieStore.getAll()

// 设置cookie
cookieStore.set()

以上这些方法都是基于Promise设计,支持对所有Cookie属性进行精准控制,同时提供了监听Cookie变化的能力,使用示意:

// cookieStore也是全局的
cookieStore.addEventListener("change", (event) => {
  console.log("cookie改变");
});

兼容性

CookieStore已被主流浏览器全面支持,如下截图所示:

CookieStore的兼容性

对于需要兼容低版本浏览器的场景,可通过@ungap/cookie-store等polyfill库补全功能,无需担心技术落地问题。

if (!'CookieStore' in window) {
  // 使用降级方案
}

三、CookieStore常见使用示意

CookieStore的API设计极为简洁,核心围绕“增、删、改、查、监听”五大场景,所有方法均返回Promise,支持async/await语法。

1. 新建/修改Cookie:set()方法

set()方法是CookieStore的核心,既可以新建Cookie,也可以修改已有Cookie(通过键名匹配)。

它支持两种参数格式:键值对+配置对象,或包含完整信息的单个对象。

// 方式1:键名、键值与配置分离
async function setUserCookie() {
  await cookieStore.set(
    'username', // 键名
    'zhangxinxu', // 键值
    {
      maxAge: 3600, // 存活时间(秒),替代传统expires
      path: '/', // 作用路径
      domain: 'zhangxinxu.com', // 作用域
      secure: true, // 仅HTTPS下生效
      sameSite: 'strict' // 防止CSRF攻击,可选strict/lax/none
    }
  );
  console.log('Cookie设置成功');
}
// 方式2:单个对象参数(更推荐,结构清晰)
async function setTokenCookie() {
  await cookieStore.set({
    name: 'token',
    value: '欢迎购买我的新书:html并不简单...',
    expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 过期时间(Date对象)
    secure: true,
    sameSite: 'lax'
  });
}

注意:当设置的Cookie键名已存在时,set()方法会自动覆盖原有Cookie,无需手动删除;maxAgeexpires二选一即可,maxAge以秒为单位,更符合前端开发习惯。

2. 读取Cookie:get()与getAll()方法

读取Cookie时,get()用于获取指定键名的单个Cookie,getAll()用于获取所有符合条件的Cookie(支持按path、domain过滤),彻底告别字符串拆分的麻烦。

// 读取单个Cookie
async function getUserCookie() {
  const cookie = await cookieStore.get('username');
  if (cookie) {
    console.log('用户名:', cookie.value);
    console.log('过期时间:', cookie.expires);
  } else {
    console.log('Cookie不存在');
  }
}

// 读取所有Cookie
async function getAllCookies() {
  // 获取所有Cookie
  const allCookies = await cookieStore.getAll();
  console.log('所有Cookie:', allCookies);

  // 按路径过滤Cookie
  const rootCookies = await cookieStore.getAll({ path: '/' });
  console.log('根路径Cookie:', rootCookies);
}

返回的Cookie对象包含namevalueexpirespathdomain等完整属性,可直接用于业务逻辑处理,无需二次解析。

3. 删除Cookie:delete()方法

删除Cookie时,只需指定Cookie的键名及对应的pathdomain(需与设置时一致,否则无法匹配),操作简洁且不易出错。

async function deleteUserCookie() {
  await cookieStore.delete('username', {
    path: '/',
    domain: 'example.com' // 若设置时指定了domain,删除时必须一致
  });
  console.log('Cookie删除成功');
}

注意:若删除时未指定pathdomain,默认匹配当前页面的pathdomain,若与设置时的属性不匹配,会导致删除失败,这一点与传统Cookie操作规则一致。

4. 监听Cookie变化:addEventListener()

CookieStore最强大的特性之一是支持监听Cookie的变化(新增、修改、删除),这在需要实时响应Cookie状态的场景(如登录状态同步)中极为实用。

// 监听Cookie变化
function listenCookieChange() {
  cookieStore.addEventListener('change', (event) => {
    console.log('Cookie变化类型:', event.changed.length ? '新增/修改' : '删除');
    // 变化的Cookie列表
    event.changed.forEach(cookie => {
      console.log('修改的Cookie:', cookie.name, '->', cookie.value);
    });
    // 删除的Cookie列表
    event.deleted.forEach(cookie => {
      console.log('删除的Cookie:', cookie.name);
    });
  });
}

// 初始化监听
listenCookieChange();

当页面中任何Cookie发生变化时,change事件都会被触发,通过event.changedevent.deleted可清晰区分变化类型,实现登录状态同步、权限更新等场景的无缝衔接。

宋玉抛媚眼

四、CookieStore的核心优势总结

对比传统的document.cookie,CookieStore的优势堪称降维打击:

  1. 异步非阻塞:基于Promise的异步操作,避免同步操作阻塞页面渲染,提升前端性能。
  2. 语法简洁直观:告别字符串拼接与拆分,通过方法调用实现精准操作,代码可维护性大幅提升。
  3. 完整的错误处理:支持try/catch捕获操作异常,如设置Cookie时的格式错误、跨域Cookie访问限制等,排错更高效。
  4. 强大的监听能力:实时响应Cookie变化,无需轮询,简化登录、权限等场景的业务逻辑。
  5. 批量操作支持:通过getAll()实现批量读取,配合过滤条件可精准获取目标Cookie。

宋玉噤声手势

五、使用注意事项与最佳实践

尽管CookieStore优势显著,但在使用过程中仍需注意这些细节:

  • 同源策略限制:与传统Cookie一致,CookieStore无法访问跨域Cookie,仅能操作当前域名下的Cookie。
  • Secure属性要求:当Cookie设置为secure: true时,仅能在HTTPS协议下操作,本地开发时可使用localhost绕过该限制。
  • SameSite属性配置:为防止CSRF攻击,建议设置sameSite:'strict''sameSite:'lax',若需要跨域访问Cookie,可设置为sameSite:'none'(需配合secure:true)。
  • 兼容性处理:针对不支持CookieStore的浏览器,可使用polyfill库兜底,代码示例如下:
// 引入polyfill(通过CDN或npm安装)
<script src="https://cdn.jsdelivr.net/npm/@whatwg-node/cookie-store@0.2.3/cjs/index.min.js"></script>

或者:

// 封装兼容方法
async function getCookie(name) {
  if ('CookieStore' in window) {
    return (await cookieStore.get(name))?.value;
  } else {
    // 传统方式兜底
    const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
    return match ? match[2] : null;
  }
}

六、结语

document.cookie的字符串拼接,到CookieStore的异步化、标准化操作,前端Cookie管理的效率实现了质的飞跃。

CookieStore不仅解决了传统方式的诸多痛点,还通过监听能力拓展了Cookie的应用场景,是现代前端开发中值得优先采用的技术方案。

无论是简单的登录状态存储,还是复杂的权限管理场景,CookieStore都能以简洁、高效的方式满足需求。

现在就拿起这项技术,告别繁琐的Cookie操作,让代码更优雅、更易维护吧!

宋玉飞吻

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11954

(本篇完)

🔲 ☆

醒醒,该使用CookieStore新建和管理cookie了

by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=11954
本文可全文转载,但需要保留原作者、出处以及文中链接,AI抓取保留原文地址,任何网站均可摘要聚合,商用请联系授权。

一、为什么要抛弃传统Cookie操作?

在前端开发的长河中,Cookie始终扮演着重要角色。

从用户身份识别到状态维持,它是浏览器与服务器之间轻量通信的核心载体。

但长期以来,我们操作Cookie的方式始终停留在通过document.cookie拼接字符串的“原始阶段”,不仅代码冗余易出错,还无法应对异步场景下的复杂需求。

比方说,我们要设置一个Cookie,需要先获取,然后再手动进行键值对+属性的字符串拼接,例如:

document.cookie = "name=zhangxinxu; max-age=3600; path=/"

这就很麻烦,属性顺序、符号格式稍有偏差就会导致失效。

宋玉摊手

然后,我们要获取Cookie的时候,也是先获取,再字符串split分隔,再去匹配,代码冗长,还不支持批量操作。

比方说,我想一次性删除多个符合条件的Cookie,需循环遍历逐个处理。

正是因为上面这些不足,CookieStore API才应运而生。

它将Cookie操作封装为标准化的异步方法,让Cookie管理变得简洁、可控。

二、CookieStore对象特性概览

CookieStore是浏览器提供的现代API,隶属于Window对象,用于以异步、面向对象的方式操作Cookie。

包含的方法名如下所示:

// 删除cookie
cookieStore.delete()

// 获取cookie
cookieStore.get()

// 获取所有cookie
cookieStore.getAll()

// 设置cookie
cookieStore.set()

以上这些方法都是基于Promise设计,支持对所有Cookie属性进行精准控制,同时提供了监听Cookie变化的能力,使用示意:

// cookieStore也是全局的
cookieStore.addEventListener("change", (event) => {
  console.log("cookie改变");
});

兼容性

CookieStore已被主流浏览器全面支持,如下截图所示:

CookieStore的兼容性

对于需要兼容低版本浏览器的场景,可通过@ungap/cookie-store等polyfill库补全功能,无需担心技术落地问题。

if (!'CookieStore' in window) {
  // 使用降级方案
}

三、CookieStore常见使用示意

CookieStore的API设计极为简洁,核心围绕“增、删、改、查、监听”五大场景,所有方法均返回Promise,支持async/await语法。

1. 新建/修改Cookie:set()方法

set()方法是CookieStore的核心,既可以新建Cookie,也可以修改已有Cookie(通过键名匹配)。

它支持两种参数格式:键值对+配置对象,或包含完整信息的单个对象。

// 方式1:键名、键值与配置分离
async function setUserCookie() {
  await cookieStore.set(
    'username', // 键名
    'zhangxinxu', // 键值
    {
      maxAge: 3600, // 存活时间(秒),替代传统expires
      path: '/', // 作用路径
      domain: 'zhangxinxu.com', // 作用域
      secure: true, // 仅HTTPS下生效
      sameSite: 'strict' // 防止CSRF攻击,可选strict/lax/none
    }
  );
  console.log('Cookie设置成功');
}
// 方式2:单个对象参数(更推荐,结构清晰)
async function setTokenCookie() {
  await cookieStore.set({
    name: 'token',
    value: '欢迎购买我的新书:html并不简单...',
    expires: new Date(Date.now() + 24 * 60 * 60 * 1000), // 过期时间(Date对象)
    secure: true,
    sameSite: 'lax'
  });
}

注意:当设置的Cookie键名已存在时,set()方法会自动覆盖原有Cookie,无需手动删除;maxAgeexpires二选一即可,maxAge以秒为单位,更符合前端开发习惯。

2. 读取Cookie:get()与getAll()方法

读取Cookie时,get()用于获取指定键名的单个Cookie,getAll()用于获取所有符合条件的Cookie(支持按path、domain过滤),彻底告别字符串拆分的麻烦。

// 读取单个Cookie
async function getUserCookie() {
  const cookie = await cookieStore.get('username');
  if (cookie) {
    console.log('用户名:', cookie.value);
    console.log('过期时间:', cookie.expires);
  } else {
    console.log('Cookie不存在');
  }
}

// 读取所有Cookie
async function getAllCookies() {
  // 获取所有Cookie
  const allCookies = await cookieStore.getAll();
  console.log('所有Cookie:', allCookies);

  // 按路径过滤Cookie
  const rootCookies = await cookieStore.getAll({ path: '/' });
  console.log('根路径Cookie:', rootCookies);
}

返回的Cookie对象包含namevalueexpirespathdomain等完整属性,可直接用于业务逻辑处理,无需二次解析。

3. 删除Cookie:delete()方法

删除Cookie时,只需指定Cookie的键名及对应的pathdomain(需与设置时一致,否则无法匹配),操作简洁且不易出错。

async function deleteUserCookie() {
  await cookieStore.delete('username', {
    path: '/',
    domain: 'example.com' // 若设置时指定了domain,删除时必须一致
  });
  console.log('Cookie删除成功');
}

注意:若删除时未指定pathdomain,默认匹配当前页面的pathdomain,若与设置时的属性不匹配,会导致删除失败,这一点与传统Cookie操作规则一致。

4. 监听Cookie变化:addEventListener()

CookieStore最强大的特性之一是支持监听Cookie的变化(新增、修改、删除),这在需要实时响应Cookie状态的场景(如登录状态同步)中极为实用。

// 监听Cookie变化
function listenCookieChange() {
  cookieStore.addEventListener('change', (event) => {
    console.log('Cookie变化类型:', event.changed.length ? '新增/修改' : '删除');
    // 变化的Cookie列表
    event.changed.forEach(cookie => {
      console.log('修改的Cookie:', cookie.name, '->', cookie.value);
    });
    // 删除的Cookie列表
    event.deleted.forEach(cookie => {
      console.log('删除的Cookie:', cookie.name);
    });
  });
}

// 初始化监听
listenCookieChange();

当页面中任何Cookie发生变化时,change事件都会被触发,通过event.changedevent.deleted可清晰区分变化类型,实现登录状态同步、权限更新等场景的无缝衔接。

宋玉抛媚眼

四、CookieStore的核心优势总结

对比传统的document.cookie,CookieStore的优势堪称降维打击:

  1. 异步非阻塞:基于Promise的异步操作,避免同步操作阻塞页面渲染,提升前端性能。
  2. 语法简洁直观:告别字符串拼接与拆分,通过方法调用实现精准操作,代码可维护性大幅提升。
  3. 完整的错误处理:支持try/catch捕获操作异常,如设置Cookie时的格式错误、跨域Cookie访问限制等,排错更高效。
  4. 强大的监听能力:实时响应Cookie变化,无需轮询,简化登录、权限等场景的业务逻辑。
  5. 批量操作支持:通过getAll()实现批量读取,配合过滤条件可精准获取目标Cookie。

宋玉噤声手势

五、使用注意事项与最佳实践

尽管CookieStore优势显著,但在使用过程中仍需注意这些细节:

  • 同源策略限制:与传统Cookie一致,CookieStore无法访问跨域Cookie,仅能操作当前域名下的Cookie。
  • Secure属性要求:当Cookie设置为secure: true时,仅能在HTTPS协议下操作,本地开发时可使用localhost绕过该限制。
  • SameSite属性配置:为防止CSRF攻击,建议设置sameSite:'strict''sameSite:'lax',若需要跨域访问Cookie,可设置为sameSite:'none'(需配合secure:true)。
  • 兼容性处理:针对不支持CookieStore的浏览器,可使用polyfill库兜底,代码示例如下:
// 引入polyfill(通过CDN或npm安装)
<script src="https://cdn.jsdelivr.net/npm/@whatwg-node/cookie-store@0.2.3/cjs/index.min.js"></script>

或者:

// 封装兼容方法
async function getCookie(name) {
  if ('CookieStore' in window) {
    return (await cookieStore.get(name))?.value;
  } else {
    // 传统方式兜底
    const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
    return match ? match[2] : null;
  }
}

六、结语

document.cookie的字符串拼接,到CookieStore的异步化、标准化操作,前端Cookie管理的效率实现了质的飞跃。

CookieStore不仅解决了传统方式的诸多痛点,还通过监听能力拓展了Cookie的应用场景,是现代前端开发中值得优先采用的技术方案。

无论是简单的登录状态存储,还是复杂的权限管理场景,CookieStore都能以简洁、高效的方式满足需求。

现在就拿起这项技术,告别繁琐的Cookie操作,让代码更优雅、更易维护吧!

宋玉飞吻

本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:https://www.zhangxinxu.com/wordpress/?p=11954

(本篇完)

🔲 ☆

在当前线程周期回调函数

FunctionScheduler 可在当前线程周期回调函数

#include <folly/experimental/FunctionScheduler.h>
#include <iostream>
#include <gtest/gtest.h>
using namespace folly;
using namespace std;
TEST(repeatfunc,repeatfunc) { 
  FunctionScheduler fs;
  fs.addFunction([]() { cout << "Hello, world!" << endl; }, 1s);
  fs.start();
  this_thread::sleep_for(10s);
  fs.cancelAllFunctions();
}

只需要addFunction时指明要回调的函数和时间间隔。调用start启动,当不再需要回调时调用cancelAllFunctions. 本例中,每隔1s打印一次”Hello, world!”,共打印10次。

可以看到,比使用folly eventbase的asynctimer简单很多。

🔲 ⭐

从一个简单的实例看 JavaScript 的异步编程进化历

回调地狱

很久没有进行过创作了,也感觉到了自己的不足。这一篇文章是对于 JavaScript 异步编程的一个 整理
希望自己更多的成为一个创造者,而不是只会看,会用,还需要深入理解到原理吧。

例子如下:

我们有 A, B, C, D 四个请求获取数据的函数(函数自己实现),
C 依赖 B 的结果,D 依赖 ABC 的结果,最终输出 D 的结果。

版本一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 伪代码
function A(callbak) {
ajax(url, function(res) {
callbak(res);
});
}
function B(callbak) {
ajax(url, function(res) {
callbak(res);
});
}
function C(data, callback) {
ajax(url, data, function(res) {
callbak(res);
});
}
function D(data1, data2, data3, callback) {
ajax(url, { data1, data2, data3 }, function(res) {
callbak(res);
});
}

A(function(resa) {
B(function(resb) {
C(resb, function(resc) {
D(resa, resb, resc, function(resd) {
console.log("this is D result:", resd);
});
});
});
});

emm…代码还是能运行,但是写法丑陋,回调地狱,如果还有请求依赖,得继续回调嵌套
性能太差,没有考虑 A 和 B 实际上是可以并发的。

例子二

函数基础实现如同例子一,但是考虑 A,B 可以并发的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 伪代码
let resa = null;
let timer = null;

A(res => {
resa = res;
});

B(resb => {
C(resb, resc => {
timer = setInterval(() => {
if (resa) {
D(resa, resb, resc, resd => {
console.log("this is D result:", resd);
timer && clearInterval(timer);
});
}
}, 100);
});
});

考虑了 A,B 的并发,使用 setInterval 轮询实现,并不一定实时。性能太差。

例子三

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 伪代码
let count = 2;
let resa = null;
let resb = null;
let resc = null;
function done() {
count--;
if (count === 0) {
D(resa, resb, resc, resd => {
console.log("this is D result:", resd);
});
}
}

A(res => {
resa = res;
done();
});
B(datab => {
C(datab, datac => {
resb = datab;
resc = datac;
done();
});
});

使用 计数器实现。性能没什么问题,但是 封装太差,写法恶心。

例子四

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 实现并发
function parallel(tasks, callback) {
let count = tasks.length;
let all = [];
tasks.forEach((fn, index) => {
fn(res => {
all[index] = res;
count--;
if (count === 0) {
callback(all);
}
});
});
}
// 实现串行
function waterfall(tasks, callback) {
let count = tasks.length;
function loop(...args) {
let task = tasks.shift();
task.apply(
null,
args.concat([
(...res) => {
count--;
if (count === 0) {
return callback(res);
}
loop(...res);
}
])
);
}
loop();
}
function A(cb = () => {}) {
setTimeout(() => {
cb("a");
}, 2000);
}
function B(cb = () => {}) {
setTimeout(() => {
cb("b");
}, 1000);
}
function C(datab, cb = () => {}) {
setTimeout(() => {
cb(datab, "c");
}, 1000);
}
function D(data, datab, datac, cb = () => {}) {
cb("d");
}
parallel(
[
A,
cb => {
waterfall([B, C], (datab, datac) => {
cb(datab, datac);
});
}
],
data => {
const [resa, [resb, resc]] = data;
D(resa, resb, resc, resd => {
console.log("this is D result:", resd);
});
}
);

模仿 async.js 提炼出来了 waterfall,parallel,两个流程控制函数。还不错。
但是写法还是麻烦,对于 A,B,C 的实现有要求。得自己考虑好每一次 callback 的值。

async.js 是我认为在目前 JavaScript callback 的终极解决方案了(没用过 fib.js..

推荐查看 github async.js 源码。

waterfall 可以考虑使用函数式的形式实现:

1
2
3
4
5
6
7
8
9
10
function pipe(...fnList) {
return function(...args) {
const fn = fnList.reduceRight(function(a, b) {
return function(...subArgs) {
return b.apply(this, [].concat(subArgs, a));
};
});
return fn.apply(this, args);
};
}

例子五

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function A() {
return fetch("http://google.com");
}
function B() {}
function C() {}
function D() {}

Promise.all[(A(), B().then(b => C(b)))]
.then(([resa,{resb,resc}) => {
return D(resa,resb,resc);
})
.then(resd => {
console.log("this is D result:", resd);
});

使用 Promise 来代替 之前的 callback。好评。
用 Promise.all 来控制并发,使用 .then 串行请求,整体看起来非常舒服了,脱离了回调地狱。

例子六

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function A(cb) {
setTimeout(() => {
cb("a");
}, 2000);
}
function B(cb) {
setTimeout(() => {
cb("b");
}, 1000);
}
function C(datab, cb) {
setTimeout(() => {
cb("c");
}, 1000);
}
function D(dataa, datab, datac, cb) {
setTimeout(() => {
cb("d");
}, 1000);
}
function thunk(fn) {
return function(...args) {
return function(callback) {
fn.call(this, ...args, callback);
};
};
}
function scheduler(fn) {
var gen = fn();

function next(data) {
var result = gen.next(data);
if (result.done) return;
// 如果没结束就继续执行
result.value(next);
}

next();
}

// generator 实际代码
function* generatorTask() {
const resa = yield thunk(A)();
const resb = yield thunk(B)();
const resc = yield thunk(C)(resb);
const resd = yield thunk(D)(resa, resb, resc);
console.log("this is D result:", resd);
return null;
}

scheduler(generatorTask);

使用 generator + callback 来控制流程顺序,还是同步写法,看起来还是挺牛逼的。
但是 generator 不会自动执行,需要自己手动写一个执行器,并且依赖于 thunk 函数。麻烦!
等等。。又全变成了串行?垃圾

例子七

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function A() {
return new Promise(r =>
setTimeout(() => {
r("a");
}, 2000)
);
}
function B() {
return new Promise(r =>
setTimeout(() => {
r("b");
}, 1000)
);
}
function C(datab) {
return new Promise(r =>
setTimeout(() => {
r("c");
}, 1000)
);
}
function D(dataa, datab, datac) {
return new Promise(r =>
setTimeout(() => {
r("d");
}, 1000)
);
}
function scheduler(fn) {
var gen = fn();

function next(data) {
var result = gen.next(data);
if (result.done) return;
// 如果没结束就继续执行
result.value.then(next);
}

next();
}

// generator 实际代码
function* generatorTask() {
const [resa, { resb, resc }] = yield Promise.all([
A(),
B().then(resb => C(resb).then(resc => ({ resb, resc })))
]);
const resd = yield D(resa, resb, resc);
console.log("this is D result:", resa, resb, resc, resd);
return resd;
}

scheduler(generatorTask);

抛弃了 thunk 函数,修改了一下 A,B,C,D。的实现以及 generator 执行函数 scheduler。 结合了 Promise 重新实现了并发和串行。
再等等??好麻烦啊。。然后并发好像和 generator 没什么关系吧。果然还是 Promise 大法好。

关于 generator 的自动执行建议直接看 github tj/co 的源码。

例子八

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function A() {
return fetch("http://google.com");
}
// ...B,C,D
async function asyncTask() {
const resa = await A();
const resb = await B();
const resc = await C(resb);
const resd = await D(resa, resb, resc);
return resd;
}

asyncTask().then(resd => {
console.log("this is D result:", resd);
});

使用 Promise 结合 async/await 的形式 ,看起来非常简洁。也不用自己写执行器了,舒服。
但是和上面有几个版本出现了一样的问题,没有考虑并发的情况,导致性能下降。

例子九,终极方案?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ...B,C,D
async function asyncBC() {
const resb = await B();
const resc = await c(resb);
return { resb, resc };
}
async function asyncTask() {
// const [resa,{resb,resc}] = await Promise.all([A(), B().then(resb=>C(resb)]);
const [resa, { resb, resc }] = await Promise.all([A(), asyncBC()]);
const resd = await D(resa, resb, resc);
return resd;
}
asyncTask().then(resd => {
console.log("this is D result:", resd);
});

使用 Promise.all 结合 async/await 的形式,考虑了并发和串行,写法简洁。
应该算是目前的终极方案了。 async/await 作为 generator 语法糖还是非常的甜的。

例子十 使用 RxJs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { defer, forkJoin } from "rxjs";
import { mergeMap, map } from "rxjs/operators";

function A() {
return fetch("https://cnodejs.org/api/v1/topics").then(res => res.json());
}
function B() {
return fetch("https://cnodejs.org/api/v1/topics").then(res => res.json());
}
function C() {
return fetch("https://cnodejs.org/api/v1/topics").then(res => res.json());
}
function D(...args) {
return fetch("https://cnodejs.org/api/v1/topics")
.then(res => res.json())
.then(res => [...args, res]);
}
// A, B, C, D 函数必须返回 Promise
// 使用 defer 产生一个 Observable
const A$ = defer(() => A());
// pipe 类型 Promise 链中 的 then
const BC$ = defer(() => B()).pipe(
// mergeMap 映射成 promise 并发出结果
mergeMap(resB => {
// 使用 map 产生新值
return defer(() => C(resB)).pipe(map(resC => [resB, resC]));
})
);

// forkJoin 类似 Promise.all 并发执行多个 Observable
forkJoin(A$, BC$)
.pipe(mergeMap(([resa, [resb, resc]]) => D(resa, resb, resc)))
.subscribe(resd => {
console.log("this is D result:", resd); // <------- fnD 返回的结果
});

使用 rxjs 来构建流式的请求过程。结构还是非常清晰的,但是相对繁琐,概念也比 原生的 Promise 和 await 要多

不过 rxjs 操作符巨多,掌握之后,可以做更多的事情


结语:

从上面几个例子我们可以窥探到 JavaScript 对于异步编程体验的一个非常大的进步。

但是同时我们其实可以看到不论是 generator 还是 async/await。其实更多的是基于 Promise 之上的一些语法简化。
没有从 callback 过渡到 Promise 的时候那种真正心灵上的愉悦。

感谢 @墨水 之前在内部分享提供的 demo 版本。

❌