Node.js 8 中的 util.promisify

Node.js 8 内建了“Promise 化”功能,可以把之前回调模式的方法,很简单的封装成支持 Promise 的方法,只需要调用 util.promisify(原函数) 即可。对于非 Node.js 标准风格的函数,也提供自定义转换函数的功能。

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 实例。

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

继续阅读“Node.js 8 中的 util.promisify”

使用 Windows Linux Subsystem 配置开发环境

最新版的 Windows 10 提供了”Windows Subsystem Linux“功能,可以在 Windows 下跑 Linux,大大提升开发效率。本文将介绍如何在 WSL 下搭建 PHP 开发环境。

微软前些日子公布了 Windows Subsystem Linux(下简称WSL),当时还只有 Insider Preview 支持安装,作为普通用户的我就没有尝试。这两天不知道伴随哪次更新,标准的专业版也可以安装了,就试着配了一下,发现效果还比较理想,基本可以替代之前的 IIS 或者 VirtualBox 方案。这里简单记录一下。

(2016-12-02 更新)随着最近一次 1607 更新,家庭版也可以安装了,效果基本一样。

安装 WSL

开始 > 设置 > 系统 > 应用和功能 > 拖到最下面“程序和功能”。然后点击“启用或关闭 Windows 功能”,找到“适用于 Linux 的 Windows 子系统(beta)”,启动之。

启用 Windows Subsystem Linux

之后系统会要求重启,听它的。

然后打开命令行(或者 PowerShell,下同),输入 bash,提示需要切换到开发者模式。按照提示切过去,然后再次运行 bash,系统会安装需要的组件。这个过程会比较久,大概源都在国外吧。安装完成后又要重启,之后 WSL 就算安装成功。

再次打开命令行,输入 bash,就可以进入 Linux 子系统了。

安装 PHP,配置Apache

WSL 其实就是 Ubuntu 14.04.5,所以安装包直接用 apt-get 就可以。嫌慢的话可以换上国内的源,不赶时间就让它慢慢更新。

sudo apt-get update
sudo apt-get upgrade

然后添加 PHP 源。

sudo add-apt-repository ppa:ondrej/php

需要注意,以前那个 ppa:ondrej/php5-5.6 的源已经被移除了,5.5 到 7.1 都用 ondrej/php 这个新源。如果非要用 5.4 的话,还有个 php5-oldstable 的源。

这里顺便把几个可能用到的组件也装上。

sudo apt-get install php
sudo apt-get install php-mysql
sudo apt-get install php-xdebug
sudo apt-get install php-apcu
sudo apt-get install php-xml
sudo apt-get install php-mcrypt

这次我本来打算用 Nginx,装上之后启动不了,Google 之,似乎是 WSL 对 IP v6 的支持有问题,遂卸掉继续用 Apache。反正本地开发嘛,性能并发不是什么问题。

Ubuntu 自带 Apache 2.4.7,直接 sudo service apache2 start 启动就好。不过这里遭遇到 vhost 的问题,我配置的虚机怎么都不生效,最后在 stack overflow 上找到解决方案,说是 000-default 和自定义的主机冲突,禁用掉默认配置就好了。

sudo a2enmod vhost_alias
sudo a2dissite 000-default
sudo a2ensite 001-mysite

然后还需要调整一下模块,比如要进行 WordPress 开发,就要保证重定向生效。

sudo a2enmod php7.1
sudo a2enmod rewrite

另外,Apache 2.4 要求配置目录的“访问条件”,不然不能访问(会返回 “403 Forbidden”,改目录权限也没有用),所以要增加一行:

<Directory "/path/to/my/site">
  ...
  Require all granted # 允许所有来源
  AllowOverride All # 允许重定向,WordPress 必备
  ...
</Directory>

全部配置好之后,重启 apache,访问配置的域名,就可以了。

配置 Xdebug

配置好 PHP 之后,开启 Xdebug 并不复杂。以我安装的 PHP7.1为例,安装之后,配置文件都在 /etc/php/7.1/apache2/conf.d/ 这个文件夹里,找到 20-xdebug.ini 并添加以下几行,然后重启 apache,即可。

xdebug.remote_enable=on
xdebug.remote_autostart=on
xdebug.idekey=PHPSTORM

使用 cmder 替换 PowerShell

PowerShell 也好,自带的命令行也好,都不好用。要么字体难看得要死,要么中文输入有问题,而且不支持多窗口。这里推荐 cmder,很好用,体验提升明显。

cmder 支持打开窗口并执行启动脚本,可以一键进入 Linux 控制台,只要按下图进行配置即可。

配置 cmder 支持 WSL

然后再修改启动时默认的 tab。

修改 cmder 启动时默认打开的 tab 类型

启用 Ruby

这个版本的 Ubuntu 集成了 rvm,通过 rvm list 可以看到已经安装了 Ruby 2.3.0。但是直接运行 ruby 却提示没有安装,运行 rvm use 2.3.0 提示

RVM is not a function, selecting rubies with ‘rvm use …’ will not work.
You need to change your terminal settings to allow shell login.
Please visit https://rvm.io/workflow/screen/ for example.

这个很奇怪,以前没有遇见过。通过 Google 得知,这是因为默认的登录方式没有读取 ~/.bash_login 里的配置信息,没有加载到 rvm 需要的方法,所以需要在启动 WSL 的时候增加参数 --login,所以回到上一步,修改启动脚本,问题解决。

%windir%\system32\bash.exe ~ -cur_console:p --login

(这里我还去掉了zsh,因为这台电脑似乎没有安装这个包,我不知道是不是默认不安装的问题。)

安装 MySQL

[2017-03-25 更新] 下面提到的坑跟我用“旧”配置——0.6.0 有关。这个配置参考自 digital ocean 的文章,实际上 Oracle 已经推出了新版本的配置文件,使用新版配置文件安装 MySQL 就不会遇到这个问题。新版配置文件的链接在此。其它步骤相同。

原以为安装 MySQL 最简单,没想到也有坑……主要是 5.7 的坑。

我比较喜欢新版本,所以自然就准备上 5.7。Ubuntu 自带的源是 5.5 或者 5.6,所以要先更新一下:

wget http://dev.mysql.com/get/mysql-apt-config_0.6.0-1_all.deb
sudo dpkg -i mysql-apt-config_0.6.0-1_all.deb
sudo apt-get update

好了,接下来直接安装就好:

sudo apt-get install mysql-server

根据提示信息安装 MySQL Server 5.7,设置 root 密码。这个阶段多半不会出问题。安装完成之后,运行 mysql -uroot -p,输入刚才设置的密码,不出意外的话就可以进入 MySQL 控制台了。

进入 MySQL 控制台

接下来我们通常要设置开发用户,此时问题也就来了。(这个问题在我的几台机器上,包括 VPS 表现不一,时有时无,所以我不太确定到底什么情况下会发生。)

CREATE USER 'someone'@'localhost' IDENTIFIED BY 'password';

执行这句 SQL 将创建一个用户,看起来完全不会有问题,结果却报错了:ERROR 1054 (42S22): Unknown column ‘plugin’ in ‘mysql.user’(报错的列可能不同,但原因是一样的)。Google 之,原来随着 MySQL 版本升级,系统内建表的结构也发生着变化,5.7 需要的字段比起 5.5 甚至 5.4 多了不少。虽然我们直接安装的 5.7,也不耽误它按照老的结构创建表……

这个时候有两个选择,一是手工把差的列加上,另一个则是运行官方提供的升级脚本 mysql_upgrade。为表达对官方的信任,我选择了后者。退出 MySQL,回到 SSH,执行脚本:

mysql_upgrade --upgrade-system-tables -u root -p

升级完成之后,再 mysql -uroot -p,输入密码,登录不进去了!WTF!怎么回事?!

又是一通 Google,原来 5.7 之后,开发团队移除了 mysql.user 表里的 password 字段,用 authentication_string 取而代之,然而这个字段并没有复制之前 password 的值,所以原来的密码就失效了!这是什么鬼……

这里我们又有两个选择,一是想办法修改 root 密码,二是卸了重装……因为我当时没找到真正的原因(就是上面这个),所以选择了后者。现在我推荐一个解决方案:

  1. 安装后,进入 MySQL 控制台,输入 DESC mysql.user;
  2. 如果返回 45 rows(行),则不需要升级系统内建表,改干嘛干嘛;如果少于这个数,就要执行 mysql_upgrade
  3. 刚才的窗口不要动,新开一个窗口,执行 mysql_upgrade --upgrade-system-tables -u root -p
  4. 执行完毕回到刚才的窗口,在 MySQL 环境下执行
UPDATE `mysql`.`user`
SET `authentication_string`=PASSWORD('password')
WHERE `user`='root';

文件互访

在 WSL 里访问系统中的文件比较容易,使用 /mnt/盘符/路径 即可。

如果要在 Windows 里访问 WSL 里的文件,它们位于 C:\Users\你的用户名\AppData\Local\lxss\

有待解决的问题

有了 WSL,各种依赖 Linux 环境的东西都可以随便跑了,利用 apt-get 安装更新也都很方便。侦听端口要完全没有问题。但其实两个环境是隔绝的,Windows 里不能执行 Ubuntu 的东西,反之亦然。这样一来,诸如 Phpstorm 的 File Watcher 包括测试都无法正常发挥作用。解决方案有待进一步发掘。

临时解决方案就是通过命令行启动各种 watch,比如

sass watch

总结

时代在发展,科技在进步。按照 GitHub 刚刚发布的报告,微软已经是开源贡献第一大公司,Windows 拥抱 Linux 之后我们也可以在 Windows 下进行PHP开发了。世界真美好啊!

顺便Ubuntu新版本也比老版本容易配置了,当然,也可能是我之前不会。


参考:

The main PPA for PHP (5.5, 5.6, 7.0) with many PECL
Support for the new Windows 10 bash shell
手把手安装 RVM 以及为什么 RVM is not a function
How To Install MySQL on Ubuntu 14.04

本地部署weinre帮助移动开发

如何在本地电脑上搭建weinre服务,帮助我们调试远程页面(比如手机上)。

weinre是个开源项目,用来在Web开发中做远程调试的工作,相当给力。后来也捐给了Apache基金会,并且从ruby移植到了JavaScript,现在可以直接通过npm安装。在最近的广告墙大重构中,这个工具帮了我很大的忙。

安装weinre首先要装node.js,后者在Windows和Mac上直接下包就能装,并且自动配置环境,很方便;在Linux上需要自己编译一次,也不复杂,我之前有篇日志写了,现在还好使,可以看看。

node.js环境搞定后,直接用npm就可以安装weinre了,直接装在全局中最好:

sudo npm install weinre -g

接下来启动weinre,我在这里卡了一阵,大概因为不太了解端口侦听的缘故,我以为直接启动就好,结果从外面连不上(手机连不上连点反应都没有,很难排查),因为侦听的是localhost的ip,也就是127.0.0.1。后来尝试绑定内网ip,才算解决问题:

# 8081是想找一个不常用的端口,后面的ip是我在内网的ip
weinre --httpPort 8081 --boundHost 192.168.10.54

这次无论是本机还是手机都可以正常访问了。然后Mac这里可能还会遇到点小问题。虽然我关闭了防火墙,但是Unix自带的ipfw还在工作,会阻止从外面过来的访问(不知道为啥80没问题),所以要给8081端口专门的许可:

sudo ipfw add 8081 allow from any to any

之后就万事大吉了,在HTML里添加 <script src="http://192.168.10.54:8081/target/target-script-min.js#meathill"></script> (域名根据具体情况修改),然后打开 http://192.168.10.54:8081/client/#meathill 就可以查看了,如果有多台终端在调试的话,还可以点选目标机器。

好了,享受下weinre带来的方便快捷的移动端调试吧。(不自己截图了,借用了weinre官网上的图片)

weinre使用截图
weinre使用截图