分类
js

记一个 `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 在当前栈捕获到错误。

分类
js

解决 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 则是实时转换,所以是宏任务,所以出问题。

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

分类
nodejs

Node.js 里使用 Promise 的小技巧

Node.js 8 的时候,引入了 util.promisify() 方法,可以把 node-like 的回调函数改造成返回 Promise 实例的方法,我当时还写了篇博文《Node.js 8 中的 util.promisify》小记。

所以我现在写 Node.js 基本都是这种风格:

const fs = require('fs');
const {promisify} = require('util');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

刚才在推上看到两位大佬聊起这个话题,发现可以这么搞:

去查了一下 Node.js 的文档,发现这是 v10 新增的 API,升级之后即可使用。

我比较喜欢这么做:

const {promises: {readFile, writeFile}} = require('fs');

(async () => {
  let content = await readFile('1.txt', 'utf8');
  content = doSthToContent(content);
  await writeFile('2.txt', content, 'utf8');
  console.log('ok');
})();
分类
nodejs

Promise 改造 child_process.exec

child_process 是 Node.js 的一个内建模块,用于分裂出(spawn)一个子进程,执行一些特定操作。.exec() 是它的方法,接受一个参数,即要执行的 shell 命令,然后通过回调返回结果。.exec().spawn() 的不同之处在于,前者重在返回结果,后者则重在返回内容。所以当你需要执行一个命令,你并不关心执行过程中发生了什么,只要看到结果就好,那么就用 .exec();反之,假如执行过程中产生的信息对你特别有价值,你并不是特别在意结果,就应该用 .spawn()

另外,我之前在《Node.js 8 中的 util.promisify》中介绍过,Node.js 8 引入了一个新函数,位于 util 模块,叫做 promisify(),用于将回调风格的 Node.js 函数改造成 Promise 规范的函数。

OK,背景知识介绍结束。近期开发中,我需要执行一个命令,并且取得它的 stdoutstderrexit code,使用 promisify() 之后发现没有 exit code,于是只好重新写了一下,代码如下:

import {exec as BaseExec} from 'util';

function exec(command, options) {
  return new Promise((resolve, reject) => {
    let result = {};
    const cp = baseExec(command, options, (err, stdout, stderr) => {
      if (err) {
        err.stdout = stdout;
        err.stderr = stderr;
        reject(err);
        return;
      }

      result.stdout = stdout;
      result.stderr = stderr;
      if ('code' in result) {
        resolve(result);
      }
    });

    cp.on('exit', (code, signal) => {
      result.code = code;
      result.signal = signal;
      if ('stdout' in result) {
        resolve(result);
      }
    });
  });
}

希望对大家有用。

新键盘到了,FC660C,静电容,试用一下,效果还不错。略硬,段落感不强,声音不大。

分类
js

JavaScript 异步开发全攻略

之前在 GitChat 做过一次分享:《JavaScript 异步开发全攻略》。在我看来,原始内容可能不够完美,但通过后来的维护,可以把它打磨得越来越好。昨天在 SF 上回答了一个 Vuex 的 action 里使用 Promise 的问题,然后就想去补充一下这方面的内容。结果发现 GitChat 竟然不支持编辑文章,只能把内容发给运营人工修改。

索性把内容放到 Gitbook 上好了,反正 GitChat SEO 也不做,文章也自然移动到第二页了。试了试,没有被墙,很好。于是简单整理了一下,上传。

欢迎阅读,欢迎分享,因为我会不时更新新内容,请关注 star:

JavaScript 异步开发全攻略

分类
技术 教程

第一场 GitChat 总结

开始之前,先做广告吧。

GitChat 分享 《JavaScript 异步开发全攻略》

为解决异步函数的回调陷阱,开发社区不断摸索,终于折腾出 Promise/A+。它不增加新的语法,可以适配几乎所有浏览器;以队列的形式组织代码,易读好改;捕获异常方案也基本可用。这套方案在迭代中逐步完善,最终被吸收进 ES2015。不仅如此,ES2017 中还增加了 Await/Async,可以用顺序的方式书写异步代码,甚至可以正常抛出捕获错误,维护同一个栈。可以说彻底解决了异步回调的问题。 现在大部分浏览器和 Node.js 都已原生支持 Promise,很多类库也开始返回 Promise 对象,更有各种降级适配策略。Node.js 7+ 则实装了 Await/Async。如果您现在还不会使用,那么我建议您尽快学习一下。

下次直播分享 前端面试攻略:JavaScript 排序与搜索

从事前端开发的同学很多从页面仔入门,比如说我,自学比例很大,有些时候会无意中忽视一些基础,比如算法、数据结构。这些欠缺在某些时候就会显得很致命,比如说面试,或者处理大量数据的场景。所以希望这样的一场分享能够帮助大家夯实原本不太扎实的基础,将来的开发之路更加顺畅。

目前早鸟票发送中,7月13日前门票5折,19日前75折,开播当日恢复全价。

分类
教程

【修正】Promise N种用法-异步回调的问题-findLargest 解析

做慕课视频的时候,仔细琢磨了一下,发现之前讲的还是有问题,所以重新录了一遍。

分类
nodejs

Node.js 8 中的 util.promisify

Node.js 8 于上个月月底正式发布,带来了很多新特性。其中比较值得注意的,便有 util.promisify() 这个方法。

如果你已经很熟悉 Promise,请继续往下看。如果你还不熟悉 Promise,可以先跳过去看下下章:Promise 介绍

util.promisify()

虽然 Promise 已经普及,但是 Node.js 里仍然有大量依赖回调的异步函数,如果我们把每个函数都封装一遍,那真是齁麻烦齁麻烦的,比齁还麻烦。

所以 Node.js 8 就提供了 util.promisify() 这个方法,方便我们把原来的异步回调方法改成支持 Promise 的方法,接下来,想继续 .then().then().then() 搞队列,还是 await 就看实际需要了。

我们看下范例,让读取目录文件状态的 fs.stat 支持 Promise:

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);
stat('.')
  .then((stats) => {
    // Do something with `stats`
  })
  .catch((error) => {
    // Handle the error.
  });

怎么样,很简单吧?按照文档的说法,只要符合 Node.js 的回调风格,所有函数都可以这样转换。也就是说,只要满足下面两个条件,无论是不是原生方法,都可以:

  1. 最后一个参数是回调函数
  2. 回调函数的参数为 (err, result),前面是可能的错误,后面是正常的结果

结合 Await/Async 使用

同样是上面的例子,如果想要结合 Await/Async,可以这样使用:

const util = require('util');
const fs = require('fs');

const stat = util.promisify(fs.stat);
async function readStats(dir) {
  try {
    let stats = await stat(dir);
    // Do something with `stats`
  } catch (err) { // Handle the error.
    console.log(err);
  }
}
readStats('.');

自定义 Promise 化处理函数

那如果现有的使用回调的函数不符合这个风格,还能用 util.promisify() 么?答案也是肯定的。我们只要给函数增加一个属性 util.promisify.custom,指定一个函数作为 Promise 化处理函数,即可。请看下面的代码:

const util = require('util');

// 这就是要处理的使用回调的函数
function doSomething(foo, callback) { 
  // ...
}

// 给它增加一个方法,用来在 Promise 化时调用
doSomething[util.promisify.custom] = function(foo) { 
  // 自定义生成 Promise 的逻辑
  return getPromiseSomehow(); 
};

const promisified = util.promisify(doSomething);
console.log(promisified === doSomething[util.promisify.custom]);
// prints 'true'

如此一来,任何时候我们对目标函数 doSomething 进行 Promise 化处理,都会得到之前定义的函数。运行它,就会按照我们设计的特定逻辑返回 Promise 实例。

我们就可以升级以前所有的异步回调函数了。

分类
技术

让微信小程序支持 ES6 的 Promise 特性

链接如此:https://haojen.github.io/2016/11/23/wechat-app-promise/

今日实测有效。Bluebird 3.5.0

分类
技术

第三次直播总结,兼谈技术教学

前几天完成了第三场直播:《Web 永恒不变的主题:布局——Box,Flex,Grid》,这里总结一下。

坦率的说,第二场直播给我造成了一些错觉。因为第二场直播比第一场多了十个人,差不多1/3,让我以为自己取得了不小的进步,甚至上一次总结的时候还信心满满。然而,这一场观众数又给我干了回来,甚至还不如第一次。感觉尚未稳固的信心又失去了……

尤其是,第二场开播之前,有将近40个人加入我开辟的答疑群,这个人数几乎和购买课程的人数相等。但是第三场,前后只有不到10个人加了进来。我只能自我安慰,告诉自己,很多人之前已经加入了……

从准备的充分程度来说,第二场 Promise 的 N 种用法,应该是最充分的。但是从技术的实用角度来看,第三场 CSS 布局应该也不差。而且理论上,CSS布局更基础,来听的人应该更多才是——我只能认为,Promise 解决的异步回调问题,比 Grid 解决的复杂网格布局,更重要,更有价值,

希望下一场会好一点吧!

接下来谈两点想法。

干货

从选题开始,我就不想讲什么个人发展,或者前端技术展望之类的。我不是说这个主题不好,我只是觉得这个对听众来说用处不大。如果听众是一个自我驱动非常强、喜欢技术、喜欢开发的人,我相信他应该很容易找到类似的信息,清楚自己的前进方向;相反,如果用户还比较迷茫,只是试探性的踏入这个领域,听了意义不大。

因为,之后真正的学习,会耗费大量的时间,这个时候就不是听听故事便足够了,而是需要个人反复练习。另一方面,每个人的情况不一样,想要制定合适自己的学习计划,恐怕只有一对一的交流,才能产生真正的价值。

总而言之,我决定只讲干货,也就是用户听了,他就会了,按照我讲的思路,12345,上手就能干活。我认为如此一来,多学多练,构建起自己的技术体系,比听别人的故事收益更大。至少,我当年就是这么学的。

但是就结果来看,我的课卖的并不好。我觉得跟我自身品牌有很大关系,还是要继续积累。上次课讲完,有个同学找到我,说觉得我的课更干货,鼓励我坚持下去。其实这个时候听到这样子的鼓励,心里还是挺暖的。

道 & 术

上次直播到问答时间,立刻就有同学提问一个我认为我已经说清楚的问题,或者说,我PPT里明确讲到的问题。对此我也进行了反思。

我当然不会认为是同学没有认真听讲,大家都是花钱来的。反省之后,我认为,在讲述的过程当中,我想传达的是“道”,也就是所以然;但是很多同学,因为经验问题,因为视野问题,他可能只想听“术”,也就是然。

从备课的角度来说,我一定要做到逻辑自洽,不仅要讲明其然,还要讲明其所以然。所以在实践过程中,我可能更关注的是讲明其所以然,也就是原理,回归到规范、定义、浏览器实现等更基础的地方;但是听众想知道的很可能是具体操作。比如布局,我会讲,外层元素没有被浮动的子元素撑开,是因为外层元素没有触发独立的 BFC,然后 BFC 是 XXOO,所以你这个时候要想办法触发它的 BFC;但是听众其实更关心“高度塌陷”怎么解决?浮动怎么清?BFC 什么的一下听不懂就整体忽略掉了。

这是我日后需要注意的地方:减少掉书袋,讲好What How Why。


最后给下一场打个广告。

写 CSS 也要开脑洞:万能的 :checked + label 将于4月27日,下周四,晚上8点直播。这场课程,是既有基础,又有进阶的中级课程。相信会对大家有所启发,欢迎大家光临。