日志

  • 记一个 `try…catch` 异步函数的坑

    记一个 `try…catch` 异步函数的坑

    前几天遇到一个问题:想捕获异步函数的错误,但是捕获不到。我的代码大概是这样:

    try {
      (async () => {
        // 一大堆异步函数
      })();
    } catch (e) {
      if (e.message.startsWith('my error:') {
        // 我的错误,提示一下即可
      } else {
        // 某种不知名错误,继续抛出
        throw new Error(e.message);
      }
    }

    不知道读者是否发现问题,我当时是左看右看没看出来。后来只好写最小用例缩小问题范围,终于找到问题所在:异步函数前面缺少 await。于是想通了,上面的代码其实被引擎解释成:

    1. try 一个函数
    2. 这个函数的返回值是一个 Promise
    3. Promise 是正常对象,所以 try 成功
    4. 至于 Promise 里的函数是否失败,不关 try 的事,所以自然捕获不到

    所以正确的写法是:

    // 外面先套一层,不然不能用 await
    (async () => {
      try {
        // 再加上 `await`
        await (async () => {
          // 异步函数体
        })();
      } catch (e) {
        // ....
      }
    })();

    这个 await 至关重要。这是异步函数的一大特性,即 try...catch 可以捕获整个异步操作前后所有栈的异常,而不仅仅是当前函数的异常。同时我们也要注意,异步函数的调用可能并不在当前栈,也无法被 try...catch 在当前栈捕获到错误。

  • Hello,2021

    Hello,2021

    2020 可能是史无前例的不受欢迎的一年,无论是微博、朋友圈、Twitter,极少看到有人怀念它的。我不禁为 2020 感到惋惜,明明不是它的罪过。

    不过我还是要说,在我经历过的 36 个年头里,2020 年是体感速度最快的一年,我现在都还能回忆起年初的种种,好像一睁眼一闭眼,就 2021 了,还有很多想做的事情没做……好吧,还是总结一下吧。

    回顾 2020

    先说身体。去年 4 月 28 日,早上起床突然发现脚踝肿了,怀疑是痛风,于是去医院看诊。医生觉得不太像是痛风,还是扭伤的可能性大。但是检查结果血糖没控制好,骨外科的大夫说糖尿病患者可能会出现随机的关节痛。哎,果然还是走上了糖尿病患者的老路。

    跟老婆商量了一下,正好疫情逐步得到控制,健身房也即将开放,于是我们办了健身卡开始健身。我也找医生换了新药控制食欲。开始效果很明显,食欲下降明显,几乎吃两口就饱了。体重下降很快,最低到 105+kg。可能是太过冒进,身体也出现一些不适,于是 8 月份去住了一周院调整。后来减了一些运动量,加了一些食量,目前感觉好多了,体重稳定在 106~108之间。

    开始锻炼之后,心肺功能和肌肉都得到了不小的加强,基本学会自由泳。

    总结一下:

    1. 终于有一年体重达标了,可喜可贺!
    2. 利拉鲁肽对我非常有效,不过半年过去,药效已经明显下降。
    3. 低血糖不好,控糖的目的一是血糖稳定,二是稳定在合适的范围内。
    4. 无氧运动加强肌肉,提升基础代谢;有氧运动减重

    side project 部分就比较惨,书没进展,肉大师没捡起来,新接的视频也没录完……春节前一定要录完,加油。

    博客大概写了 69 篇,还行吧,草稿箱里还有几篇没写完。访问量相比去年提升很多,坚持还是有用的。今年继续努力。

    旅游彻底黄了,一年没出门。哦,不对,去了几次深圳……

    终于下定决心把车换了,老车送回北京给表姐开,换了一辆亚洲龙(Avalon)代步。去年告别了很多人,包括 Avalon JS 框架的作者。新车挺好的,未来一时半会儿不会再换了。

    NAS 的机器弄出来了,没顾上搞系统,今年继续吧。

    2021 计划

    1. 坚持锻炼,把体重降到 105 kg
    2. 去年捡起来游泳,今年希望捡起来篮球
    3. 复活肉大师,把书写完
    4. 录完欠(签)下的教学视频
    5. 把前面设想的教学视频小语言做出来,然后再录 1~2 套视频
    6. 努力写博客,去年大概挣了 $20,希望今年够得上提款
    7. 旅游方面,如果国境线如果能开放,希望可以出去一次
    8. 感谢老板支持,去年年底终于把 Navlang + 扩展发出去了。那么今年要再努力一把:
      1. 上线 Chrome Web Store 和其它浏览器应用商店
      2. 能够收获 100+ 用户
      3. 完成付费使用功能
    9. 搞定一个 Side Project

    去年身体状况从五月折腾到八月,期间想了很多,觉得还是要拼一拼,主要是不拼也未必能好。所以今年还要多努力!加油!

  • Ubuntu 配置 Nginx + Ghost

    Ubuntu 配置 Nginx + Ghost

    按照惯例,买好机器,登录进去。建议是境外服务器,可以省掉备案环节。但是不要干坏事哦。

    1. 更新系统

    apt-get update
    apt-get upgrade

    2. 创建 ssh-key

    ssh-keygen -t rsa -b 4096 -C "my-email@meathill.com"

    3. 添加 authorized_keys

    使用 ssh key 登录可以大大提升服务器的安全性。

    首先,将你的电脑上的公 key 添加到服务器 ~/.ssh/authorized_keys。接着编辑服务器上的 /etc/ssh/sshd_config,禁用密码登录。

    ChallengeResponseAuthentication no
    PasswordAuthentication no

    最后重启 ssh 服务:service ssh restart

    4. 安装 Node.js

    按照 NodeSource Node.js Binary Distributions 的指引,安装对应版本的 Node.js—— Ghost 要求 LTS,所以目前只能用 v12。

    curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
    sudo apt-get install -y nodejs

    5. 安装 MySQL/MariaDB

    Ghost 默认使用 SQLite 作为数据库,不过生产环境中 SQLite 性能不够,因此建议直接安装并使用成熟可靠的数据库软件。这里建议使用 MariaDB,开源免费。不同版本的 MariaDB 源可以在 MariaDB Repositories 找到,我一般使用清华的源:

    sudo apt-get install software-properties-common
    sudo apt-key adv --fetch-keys 'https://mariadb.org/mariadb_release_signing_key.asc'
    sudo add-apt-repository 'deb [arch=amd64,arm64,ppc64el] https://mirrors.ustc.edu.cn/mariadb/repo/10.5/ubuntu focal main'
    
    # 添加完仓库和 key 之后,就可以安装了
    sudo apt update
    sudo apt install mariadb-server

    MariaDB 的安装过程没有配置密码的环节,可以使用 重置 MariaDB root 密码 一文中介绍的方法先重置 root 密码,然后创建需要的用户和数据库。

    create user 'ghost'@'localhost' identified by 'ghost';
    create database `ghost`;
    grant all on `ghost`.* to 'ghost'@'localhost';

    6. 安装 ghost-cli

    ghost-cli 是 ghost 提供的命令行管理工具,可以大大减少我们管理 Ghost 实例的时间。

    # 安装
    npm i ghost-cli -g
    
    # 创建一个目录安装 ghost
    cd /var/www
    mkdir ghost
    cd ghost

    Ghost 要求我们不能用 root 用户维护实例,所以这时可能需要创建一个新用户,并赋予其 sudo 权限(方便操作目录权限之类的),我习惯命名为 ghost-admin:

    adduser ghost-admin
    usermod -aG sudo ghost-admin

    接着,切换到 ghost-admin,安装 ghost

    su ghost-admin
    cd /var/www
    # 改变权限
    sudo chown -R ghost-admin:ghost-admin ghost
    cd /var/www/ghost
    ghost install

    安装完成之后,执行 ghost ls,可以看到正在运行的 Ghost 实例,就是一切正常了:

    + sudo systemctl is-active ghost_fav-meathill-com
    ┌──────────────────┬────────────────┬─────────┬──────────────────────┬─────────────────────────┬──────┬─────────────────┐
    │ Name             │ Location       │ Version │ Status               │ URL                     │ Port │ Process Manager │
    ├──────────────────┼────────────────┼─────────┼──────────────────────┼─────────────────────────┼──────┼─────────────────┤
    │ my-ghost │ /var/www/ghost │ 3.31.5  │ running (production) │ http://my-ghost.com │ 2368 │ systemd         │
    └──────────────────┴────────────────┴─────────┴──────────────────────┴─────────────────────────┴──────┴─────────────────┘

    接下来,编辑 config.production.json,把数据库配置和域名配置都写进去,就基本可用了。

    7. 安装并配置 Nginx

    首先,配置源。目前 Ubuntu 20.04,代号 focal,命令如下:

    deb https://nginx.org/packages/ubuntu/ focal nginx
    deb-src https://nginx.org/packages/ubuntu/ focal nginx
    sudo apt update
    sudo apt install nginx

    增加配置文件,并进行反向代理,即把外来的访问反向代理给 Ghost 服务:

    
    cp /etc/nginx/site-available/default /etc/nginx/site-available/ghost.conf
    ln -s /etc/nginx/site-available/ghost.conf /etc/nginx/site-enabled/ghost.conf

    配置文件大体如下:

    server {
        listen 80;
        listen [::]:80;
    
        server_name my-ghost.com;
        root /var/www/ghost/system/nginx-root; # Used for acme.sh SSL verification (https://acme.sh)
    
        location / {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header Host $http_host;
            proxy_pass http://127.0.0.1:2368;
    
        }
    
        location ~ /.well-known {
            allow all;
        }
    
        client_max_body_size 50m;
    }

    最后重启 nginx 服务或者重新加载配置即可:

    # 一般建议先检查一下配置有没有问题
    nginx -t
    service nginx restart

    总结

    这次主要更新了使用 ghost-cli 安装和配置的内容,服务器也改用 MariaDB,新的技术使得安装配置都更简单了。

  • 使用 <input type=”file”> 上传 ZIP/RAR 文件

    使用 <input type=”file”> 上传 ZIP/RAR 文件

    上传文件要用到 <input type="file">,这个元素有个 accept 属性,可以用来筛选文件类型,方便用户选择。按照 MDN 的说法,这个属性的值支持以下几种形式:

    1. 合法的文件扩展名,大小写不敏感,以 . 作为开头,比如 .jpg.pdf
    2. 合法的 MIME type,不需要扩展名
    3. 媒体文件,audio/* 适配任意声音文件,video/* 适配任意视频,image/* 适配任意图片

    同时,属性值可以使用 , 连接,表示“或”的意思。注意,这里只能是单独的 ,,不能有空格,不然有空格的部分会失效。所以,比如,一个上传图片、以及 PDF 的元素就可以写成:

    <input type="file" accept="image/*,.pdf">

    使用扩展名比较方便,但是扩展名太多不方便管理,比如 .jpg.jpeg,容易漏掉,所以我更喜欢 MIME type。那么压缩文件的 MIME type 是什么呢?经过一番搜索和尝试,是:.zip,.rar,application/x-rar-compressed,application/zip,application/x-zip-compressed,application/octet-stream

  • 基于 @vue/cli 的项目配置 browserslist

    基于 @vue/cli 的项目配置 browserslist

    前些日子虽然写了 最近折腾 @babel/preset-env 的一些小心得,但其实没有正确的理解和配置 browserslist,所以今天问题又来了。

    (更多…)
  • Nuxt.js 支持 core-js 3

    Nuxt.js 支持 core-js 3

    Vue CLI 升级到 v4 之后,将内部的 core-js 依赖升级到 v3,关于 core-js v3 和 core-js v2 之间的区别,我在 最近折腾 @babel/preset-env 的一些小心得 里简单介绍过。

    升级完 Vue CLI 之后,在调用 nuxt generate 生成静态页,就会报错,因为 Nuxt.js 默认使用 core-js 2。这个时候,如果 Nuxt.js 版本在 2.6.0 之后,就只需要修改 nuxt.config.js 里的配置,指定 core-js 的版本:

    module.exports = {
      build: {
        babel: {
          presets({ isServer }) {
            return [
              [
                require.resolve('@nuxt/babel-preset-app'),
                // require.resolve('@nuxt/babel-preset-app-edge'), // For nuxt-edge users
                {
                  buildTarget: isServer ? 'server' : 'client',
                  corejs: { version: 3 }
                }
              ]
            ]
          }
        }
      }
    }

    参考文档:https://nuxtjs.org/guide/release-notes#v2.6.0

    说起来 Nuxt.js,用它发布静态页比想象中复杂,如果你想快速掌握这个技能,不妨看下我的这本小书:

  • 解决 Firefox 下的 race 问题

    解决 Firefox 下的 race 问题

    我厂有几个产品,需要从后端获取大量的信息,为了让用户能够近乎实时的看到这些信息,大部分数据都是通过 WebSocket 发给前端。这些产品在 Chrome 下表现正常,但是在 Firefox 下经常把数据格式搞乱,最终渲染失败。

    因为 Firefox DevTools 没法解析 WebSocket 数据,而且市场占有率比较低,所以我一直没有解决这个问题。前几天终于把最小可复现实例搞出来,正准备研究,结果同事已经修好了。

    预览版的 Firefox 终于可以在 DevTools 里查看 WebSocket 每一帧的数据,所以她尝试看了一下,发现从解析二进制数据的角度来看,Firefox 应该没问题。于是又回到代码,发现了一个可能产生 race 的点:

    if (data instanceof Blob) {
      data = await new Response(data).arrayBuffer();
    }

    因为服务器返回的数据是二进制,所以我需要进行一次转换,把它变成 ArrayBuffer,然后再通过 TextDecoder 转换成文本,然后处理。Response.arrayBuffer 返回的是 Promise,所以我就很自然的用 await,并且在 Chrome 上运行良好。

    但是在 Firefox 里,某些帧会后发先完成转换,a b c 变成 a c b,于是数据格式错乱,无法正常解析。我怀疑 Chrome 并没有真的把这一步保留到用户,而是同时存了两份数据,这样转换的时候直接给出数据就好,所以是微任务,不走 Event loop,不会产生 race。而 Firefox 则是实时转换,所以是宏任务,所以出问题。

    我尝试去翻了一下源码,无奈平时没看过,所以没能找到证据。如果有哪位同学刚好知道,可以在评论里告诉我。

  • 代友招聘:每日优鲜 BI 部门招前端技术经理

    代友招聘:每日优鲜 BI 部门招前端技术经理

    有个关系很好的朋友近期加盟每日优鲜,负责 BI 系统,据他描述,组内有三名初级前端,需要一个高级前端做技术经理,负责带团队。

    薪资范围:30K~40K,16个月

    工作地点:北京望京

    (更多…)
  • 面试题:如何理解加班

    面试题:如何理解加班

    前阵子参加 SF 的征文活动 一起分享你的故事,我的文章 我的编程职业生涯 有幸得到大家的支持,最终得奖:汤青松老师的《PHP Web 安全开发实战》。书前两天寄到了,还没来得及细看,也没法写书评,所以想了想,再分享一段职场经验吧。

    (更多…)
  • Perl 笔记

    Perl 笔记

    主要记录在 Windows 下使用 Perl 的经验。

    WSL

    1. 内建 Perl 环境

    安装

    我安装的是 Strawberry 版,没啥好说的,下载安装即可。