阅读视图

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

一次讲清楚 `Promise.finally()`:为什么“无论成功失败都要执行”该用它

在日常业务里,经常有这种需求:

  • 先做一件异步事(请求、弹窗、授权等);
  • 不管结果成功还是失败,后续流程都要继续。

这段代码就是一个典型例子:

this.requestSomeSubscribeMessage().finally(() => {
    this.getSomeData(item.status);
});

1. 这段代码到底是什么语法?

这是 Promise 链式调用:

  1. this.requestSomeSubscribeMessage() 返回一个 Promise;
  2. .finally(...) 注册一个“收尾回调”;
  3. 当前面 Promise 结束(fulfilled 或 rejected)时,finally 里的代码都会执行。

一句话:finally = 不管成败都执行。


2. 和 then / catch 的区别

最核心区别:

  • then:只处理成功
  • catch:只处理失败
  • finally:成功失败都执行(常用于收尾)

示例:

doSomething()
  .then((res) => {
  console.log('成功', res);
})

.catch((err) => {
  console.log('失败', err);
})

.finally(() => {
  console.log('一定会执行');
});

3. 为什么订阅消息这个场景特别适合 finally

你的业务要求是:

  • 先调起订阅弹窗;
  • 用户允许、拒绝、关闭、报错都不阻断;
  • 始终继续办理保险流程。

这正是 finally 的语义:把“不应被阻断的后续逻辑”放进统一出口。

4. 你这段代码可以怎么理解(按执行顺序)

this.requestSomeSubscribeMessage().finally(() => {
  this.getSomeData(item.Status);
});

执行过程:

  1. 调用 requestSomeSubscribeMessage(异步);
  2. 等它结束;
  3. 不管结束状态是什么,都调用 getSomeData(...)

5. 一个容易混淆的点:finally 不是拿结果用的

finally 适合做“收尾动作”,比如:

  • 关闭 loading
  • 释放锁
  • 继续不应中断的流程
  • 埋点/日志(不依赖业务结果时)

如果你要依赖成功结果(如 res.data),应该在 then 里处理。

6. async/await 的等价写法(推荐复习)

你这段逻辑也可以写成:

try {
  await this.requestSomeSubscribeMessage();
} finally {
  this.getSomeData(item.tianCaiInsuranceStatus);
}

这和 Promise 的 finally 语义一致:
try 成功或抛错,finally 都执行。

7. 实战建议(可直接记忆)

  • 看语义选方法:

    • 只成功:then
    • 只失败:catch
    • 都要执行:finally
  • 把“必须执行”的业务放 finally,最不容易漏逻辑。

  • 不要在 finally 里写依赖成功结果的代码(会让代码可读性变差)。

8. 这个案例的一句话总结

this.requestSomeSubscribeMessage().finally(...) 的含义就是:
“订阅流程结束后(不论结果),都继续办理保险。”

一文讲清楚 npm 包里的 `dependencies` 和 `devDependencies`

刚接触 npm 包管理时,很多人都会被两个字段绕住:

  • dependencies
  • devDependencies

表面看只差了一个 dev,但实际背后是两种完全不同的角色。

很多人会先形成一个直觉:

devDependencies 是开发时依赖,dependencies 是运行时依赖。

这个理解方向没错,但如果只停在这里,实际写项目时还是容易分错。

这篇文章就把这个问题彻底掰开讲明白。


一、先看结论

最简单的判断标准是这一句:

包在真正运行时还需要它,就放 dependencies
只在开发、测试、构建、打包阶段需要它,就放 devDependencies

换句话说:

dependencies

表示运行时依赖

也就是:

  • 项目启动时需要
  • 代码执行时需要
  • 用户真正使用功能时需要

devDependencies

表示开发时依赖

也就是:

  • 本地开发时需要
  • 测试时需要
  • 构建时需要
  • 打包发布时需要
  • 代码检查时需要

二、为什么这个问题总让人混淆

因为“开发时会用到”这句话,范围太大了。

你写代码的时候,当然什么都在开发时用到了:

  • 你会用 axios
  • 你会用 lodash
  • 你会用 typescript
  • 你会用 eslint

但它们的性质并不一样。

这里真正该问的,不是:

我开发时有没有用到它?

而是:

我把包发布出去后,别人安装并运行这个包时,还需不需要它?

这才是判断关键。

三、先用一个生活化的比喻理解

可以把开发 npm 包想成开一家小餐馆。

dependencies 是什么

像端上桌的食材:

  • 调料

没有这些,顾客吃不到东西。

devDependencies 是什么

像后厨工具:

  • 菜刀
  • 烤箱
  • 清洁工具

它们对做菜很重要,但顾客不需要把这些工具一起买走。

所以:

  • 跟着“成品”一起发挥作用的,是 dependencies
  • 只在“制作过程”中发挥作用的,是 devDependencies

四、最常见的理解方式

很多文章会这样解释:

dependencies

项目运行时要用的依赖

devDependencies

开发这个项目时要用的依赖

这句话没错,但还不够完整。

更准确一点,应该改成:

dependencies

项目在实际运行时必须存在的依赖

devDependencies

项目在开发、测试、构建、打包、发布时使用的依赖

注意这里多出来几个关键词:

  • 测试
  • 构建
  • 打包
  • 发布

这几个词很重要,因为很多初学者只理解了“开发”,没理解“构建”。

五、什么叫“构建时需要”?

很多人第一次看到这句话会疑惑:

TypeScript、Babel 明明不参与业务运行,为什么它们很重要?

原因很简单:

你平时写的源码,不一定是最终发布给别人运行的代码。

比如你写的是:

const add = (a: number, b: number) => a + b
export default add

这里有 TypeScript 类型标注,运行环境并不能直接拿这些类型来执行。

所以发布前,通常会经过一轮处理,变成:

const add = (a, b) => a + b
export default add

或者再进一步转成更兼容旧环境的版本。

这个“把源码处理成可发布产物”的过程,就是构建或编译。

所以:

  • typescript
  • babel
  • rollup
  • webpack
  • vite

这些通常属于 devDependencies

因为它们负责的是“做菜过程”,不是“上桌后的食物”。

六、最容易分清的一种办法

每次遇到一个依赖,不妨问自己一句:

如果把项目构建完、发布出去,用户真正使用功能时,还要不要这个依赖?

如果要

放进 dependencies

如果不要,只是帮助你开发和打包

放进 devDependencies

这个方法很稳。

七、通过例子理解

例子 1:工具函数库里用了 dayjs

import dayjs from 'dayjs'

export function formatDate(date) {
  return dayjs(date).format('YYYY-MM-DD')
}

这里 dayjs 应该放哪?

答案是:dependencies

因为你的函数真正运行时,需要调用 dayjs

不是你开发时用了它一次就结束了,而是你包的使用者在调用 formatDate 时,底层还要依赖 dayjs

例子 2:用了 eslint 做代码检查

开发时你装了:

npm install eslint -D

它是用来做什么的?

  • 检查代码规范
  • 提示潜在问题
  • 统一团队风格

项目真正运行时,需要 eslint 吗?

不需要。

所以它应该放进 devDependencies

例子 3:用了 jestvitest 做测试

测试工具只在测试阶段执行。

用户安装你的包,并不会因为要调用某个功能而去运行 jest

所以这类依赖一般都属于 devDependencies

例子 4:用了 typescript 写源码

你源码可能是 .ts 文件,但最终发布的包往往是已经编译好的 .js 文件。

也就是说,用户使用你包时,用到的是产物,不是你本地的 TypeScript 编译器。

所以 typescript 一般属于 devDependencies

例子 5:项目里使用 axios 发请求

如果你的业务代码里直接这样写:

import axios from 'axios'

export function getUser() {
  return axios.get('/api/user')
}

axios 是实际运行逻辑的一部分。

因此它通常属于 dependencies

八、为什么很多人会把依赖放错

常见原因主要有三个。

1)只从“我开发时有没有用过”来判断

这会导致判断范围过大。

因为你开发时什么都用到了,但不是所有东西都会进入运行阶段。


2)没有区分“源码”和“发布产物”

这是个很常见的坑。

很多工具只作用在源码阶段,比如:

  • 类型检查
  • 代码转换
  • 打包压缩
  • 生成声明文件

这些工具非常重要,但它们的重要性停留在“生产过程”,不是“运行过程”。

3)把“业务依赖”误认为“开发依赖”

比如平时有的产品业务里明确用到了:

  • axios
  • lodash
  • dayjs

这些都不应该因为“开发时也在用”就被扔进 devDependencies

它们是运行逻辑本身的一部分。

九、从包作者和包使用者两个视角看,会更清楚

这个问题最容易绕,是因为视角没切换。

站在包作者视角

你会觉得:

  • 我开发时用了 typescript
  • 我开发时也用了 axios
  • 我开发时也用了 eslint

感觉它们都是“开发中会用到的东西”。

没错,但这不是 npm 关心的重点。

站在包使用者视角

npm 更关心的是:

  • 用户安装你的包后,哪些依赖必须存在,代码才能跑
  • 哪些依赖只是你内部研发流程要用

这样一看,边界就清楚了:

  • 运行必须的 → dependencies
  • 研发辅助的 → devDependencies

十、一个最小的项目例子

假设你写了一个日期格式化工具包。

目录可能是这样:

src/
  index.ts
package.json
tsconfig.json

源码:

import dayjs from 'dayjs'

export function formatDate(date: string): string {
  return dayjs(date).format('YYYY-MM-DD')
}

你在开发时可能安装了这些包:

  • dayjs
  • typescript
  • rollup
  • vitest
  • eslint

那该怎么分?

放到 dependencies

  • dayjs

因为真正运行 formatDate 时要用它。

放到 devDependencies

  • typescript
  • rollup
  • vitest
  • eslint

因为它们只是帮助你:

  • 写 TypeScript
  • 打包代码
  • 测试功能
  • 检查规范

用户运行 formatDate 本身,不需要这些工具参与。

十一、一个典型的 package.json 示例

{
  "name": "demo-utils",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "dependencies": {
    "dayjs": "^1.11.0"
  },
  "devDependencies": {
    "typescript": "^5.0.0",
    "rollup": "^4.0.0",
    "eslint": "^9.0.0",
    "vitest": "^1.0.0"
  }
}

看到这个配置,可以这样理解:

  • dayjs 是成品运行时要吃的“食材”
  • typescriptrollupeslintvitest 是制作和检查过程中的“工具”

十二、再说几个常见包,方便形成直觉

常见的 dependencies

这类包通常会直接出现在业务代码执行路径里:

  • axios
  • lodash
  • dayjs
  • uuid
  • react(很多应用项目里是这样)
  • vue(很多应用项目里是这样)

常见的 devDependencies

这类包通常服务于开发流程:

  • typescript
  • eslint
  • prettier
  • jest
  • vitest
  • webpack
  • vite
  • rollup
  • babel
  • sass(很多情况下)
  • 各类构建插件、测试插件、lint 插件

十三、一个很实用的口诀

可以记一句很接地气的话:

跟着产物跑的,放 dependencies
只陪你开发的,放 devDependencies

或者再换一种说法:

用户运行时要用的,是 dependencies
你写代码时要用的工具,是 devDependencies

十四、容易踩坑的地方

坑 1:把运行依赖误放进 devDependencies

结果可能是:

  • 本地开发一切正常
  • 发布后别人安装使用时报错
  • 因为真正运行时缺依赖

这就像你做了一碗面,结果把“面”放进了工具箱,而不是放进食材清单里。

坑 2:把开发工具误放进 dependencies

结果通常不会立刻炸,但会带来一些问题:

  • 安装体积变大
  • 用户下载了不必要的包
  • 依赖树更复杂
  • 包管理更混乱

这就像你卖一碗面,还强行把菜刀、案板、烤箱一起塞给顾客。

十五、最后总结

把这件事说到最本质,其实就一句话:

dependencies 是项目运行时真正要依赖的库;
devDependencies 是项目开发、测试、构建、打包时使用的工具依赖。

判断时别问:

我开发时有没有用到它?

而要问:

用户真正运行这份代码时,还需不需要它?

需要,就是 dependencies
不需要,就是 devDependencies

十六、结尾

刚学 npm 时,dependenciesdevDependencies 看起来只是两个字段的区别;
但理解透了之后,你会发现它本质上是在帮你区分:

  • 哪些是“产品的一部分”
  • 哪些是“生产产品的工具”

这个边界一旦建立起来,很多工程化问题都会顺。

因为写代码这件事,说到底也很像开店:

食材要分清,工具也要分清。
不然厨房会乱,顾客也吃不好。


如果喜欢这篇文章,请给我点个赞:)

祝大家:码上有钱 --Larry

微信小程序:如何优雅地修改富文本(u-parse/rich-text)内部样式?

在微信小程序开发中,渲染后端返回的富文本内容是一个非常常见的需求。为了获得更好的渲染效果(比如图片自适应、图片预览、更全的标签支持),我们通常会放弃原生的 <rich-text> 组件,转而使用第三方富文本解析组件,例如基于 mp-html 封装的 <u-parse>(uView 框架内置组件)。

但在实际开发中,我们经常会遇到修改富文本内部默认样式不生效的“坑”。本文将通过一个真实案例(修改无序列表的默认缩进),带你剖析问题的原因及最终的解决方案。

场景重现:被“无视”的 CSS 样式

最近接到一个需求:产品经理希望页面中通过富文本渲染的 <ul>(无序列表)和 <ol>(有序列表)不要有默认的缩进,而是让列表的点/数字和正文保持左对齐

正常在 Web 开发中,我们只需要几行 CSS 就能搞定:

ul, ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}
li {
  list-style-position: inside !important;
}

考虑到 Vue 的作用域,我在组件的 <style scoped> 中加上了深度选择器 ::v-deep

::v-deep ul,
::v-deep ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}
::v-deep li {
  list-style-position: inside !important;
}

结果:页面上的文字依然雷打不动地缩进,样式完全没有生效!

抽丝剥茧:为什么 CSS 选不中元素?

打开微信开发者工具的 WXML 面板审查元素,我发现了第一个盲点:

u-parse(或 mp-html)在解析富文本时,并没有把 <ul><ol> 渲染成真正的 HTML 标签,而是将其转换成了带有特定 class 的 <rich-text> 节点,类名类似于 ._ul._ol.__ul 等。

既然类名变了,那我针对这些特定的 class 再次修改 CSS 规则:

::v-deep ._ul,
::v-deep ._ol,
::v-deep .__ul,
::v-deep .__ol {
  padding-left: 0 !important;
  margin-left: 0 !important;
}

结果:样式在开发者工具的 Styles 面板中成功匹配到了对应的元素,但页面渲染的文字依然存在缩进!

终极真相:原生 <rich-text> 的样式隔离黑盒

这就触及到了小程序的底层机制。

为了提高渲染效率,u-parse 在处理嵌套的富文本时,会将解析后的 DOM 树转换为一个 JSON 节点数组(nodes),并将其整体丢给小程序的原生 <rich-text> 组件去渲染。

而原生的 <rich-text> 存在一个致命的特性:它的内部是一个样式隔离的“黑盒”

<rich-text nodes="{{nodes}}"></rich-text> 内部渲染出的标签,完全不受外部 .wxss.css 样式表的影响(即使你用了 ::v-deep 且选择器成功匹配)。内部节点能且只能读取 JSON 节点树中通过 attrs.style 传递进去的 内联样式(inline-style)

解决方案:从源头注入内联样式

既然外部的 CSS 无法穿透,我们就必须改变思路:让富文本解析器在解析 HTML 字符串时,就直接把我们想要的样式以 style="..." 的形式注入到标签中。

大多数优秀的富文本解析组件都提供了这种能力。在 u-parse (或 mp-html) 中,这个属性叫 tag-style

最终代码实现

1. 在 JS 的 data 中定义标签基础样式

export default {
  data() {
    return {
      htmlContent: '<ul><li>列表项1</li><li>列表项2</li></ul>',
      // 为 u-parse 注入标签默认样式,解决 rich-text 内部无法被外部 CSS 穿透的问题
      parseTagStyle: {
        ul: 'padding-left: 0; margin-left: 0;',
        ol: 'padding-left: 0; margin-left: 0;',
        li: 'list-style-position: inside;'
      }
    };
  }
}

2. 在模板中绑定 tag-style 属性

<template>
  <view class="content-wrapper">
    <u-parse
      :html="htmlContent"
      :tag-style="parseTagStyle"
    ></u-parse>
  </view>
</template>

通过这种方式,解析器会在生成 nodes 树时,自动将 padding-left: 0; margin-left: 0; 写入到 <ul><ol>style 属性中。最终原生 <rich-text> 渲染时,完美实现了列表符号和文字靠左对齐的需求。

总结

在微信小程序中处理富文本渲染样式时,请记住这个避坑指南:

  1. 优先避免使用 CSS 深度选择器(::v-deep)去强行修改富文本内部的样式,因为一旦底层交给了 <rich-text> 渲染,CSS 就大概率会失效。
  2. 永远优先使用解析组件提供的 tag-style(标签样式)功能,在解析阶段通过内联样式的形式注入,这才是小程序环境下最稳定、最正确的做法。
❌