## tinybear 查看完整档案

### 个人动态

tinybear 收藏了文章 · 2019-01-25

# 1：准备工作

``````function init() {
container = document.createElement('div');
container.className = 'snow';
document.body.appendChild(container);
camera = new THREE.PerspectiveCamera( 75, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 ); //透视投影相机
camera.position.z = 1000;
scene = new THREE.Scene();
scene.add(camera);
renderer = new THREE.CanvasRenderer();
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
// console.log(SCREEN_WIDTH, SCREEN_HEIGHT);
var material = new THREE.ParticleBasicMaterial( { map: new THREE.Texture(particleImage) } );``````

## 2:随机生成不同位置一定数量的雪花

``````for (var i = 0; i < 200; i++) {
particle = new Particle3D( material);
particle.position.x = Math.random() * 2000 - 1000;
particle.position.y = Math.random() * 2000 - 1000;
particle.position.z = Math.random() * 2000 - 1000;
particle.scale.x = particle.scale.y =  1;
scene.add( particle );
particles.push(particle);
}``````

``````for(var i = 0; i < particles.length; i++)
{
var particle = particles[i];
particle.updatePhysics();
with(particle.position)
{
if(y < -1000) y += 2000;
if(x > 1000) x -= 2000;
else if(x <- 1000) x += 2000;
if(z > 1000) z -= 2000;
else if(z <- 1000) z += 2000;
}
}``````

## 3:雪花远小近大的效果

``````    camera.position.x += (mouseX - camera.position.x ) * 0.05;
camera.position.y += (- mouseY - camera.position.y ) * 0.05;
camera.lookAt(scene.position);
renderer.render( scene, camera );``````

## 4:雪花的自由下落

``````Particle3D = function(material){
THREE.Particle.call(this,material);
this.velocity = new THREE.Vector3(0,-8,0);
this.velocity.rotateX(randomRange(-45,45));
this.velocity.rotateY(randomRange(0,360));
this.gravity = new THREE.Vector3(0,0,0);
this.drag=1;
};
Particle3D.prototype = new THREE.Particle();
Particle3D.prototype.constructor = Particle3D;
Particle3D.prototype.updatePhysics = function(){
this.velocity.multiplyScalar(this.drag);
this.velocity.addSelf(this.gravity);
this.position.addSelf(this.velocity);
}``````

## 5:雪花旋转效果

``````THREE.Vector3.prototype.rotateY=function(angle){
cosRY = Math.cos(angle * Math.PI/180);
sinRY = Math.sin(angle * Math.PI/180);
var tempz = this.z;;
var tempx = this.x;
this.x = (tempx * cosRY) + (tempz * sinRY);
this.z = (tempx * -sinRY) + (tempz * cosRY);
}``````

## 活动页

`````` .snow {
position: fixed;
top: 0;
left: 0;
z-index: 10000;
transform: translate3d(0, 0, 0);
width: 100%;
height: 100%;
pointer-events: none;
}``````

## 总结

tinybear 评论了文章 · 2018-12-13

## Canvas制作的下雨动画

### Canvas动画基础

Canvas 2D 的API：
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D

1. 绘制第一帧图形（利用API绘图）

2. 清空画板(应用clearRect()或fillRect())

3. 绘制下一帧动画

requestAnimationFrame会告诉浏览器你要绘制一个动画。让浏览器要重绘时调用你指定的方法（callback）来绘制你的动画。

``````
function anim() {
ctx.fillStyle = clearColor;
ctx.fillRect(0,0,w,h);
for(var i in drops){
drops[i].draw();
}
requestAnimationFrame(anim);
}
``````

### 雨滴下落效果

html代码：

``````<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>霓虹雨</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
.bg {
background: #000;
overflow: hidden;
}
</style>

</head>
<body class="bg">
<canvas id="canvas-club"></canvas>
<script type="text/javascript" data-original="raindrop.js"></script>
</body>
</html>
``````

``````var c = document.getElementById("canvas-club");
var ctx = c.getContext("2d");//获取canvas上下文
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;//设置canvas宽、高
var clearColor = 'rgba(0, 0, 0, .1)';//画板背景,注意最后的透明度0.1 这是产生叠加效果的基础

function random(min, max) {
return Math.random() * (max - min) + min;
}

function RainDrop(){}
//雨滴对象 这是绘制雨滴动画的关键
RainDrop.prototype = {
init:function(){
this.x =  random(0, w);//雨滴的位置x
this.y = 0;//雨滴的位置y
this.color = 'hsl(180, 100%, 50%)';//雨滴颜色 长方形的填充色
this.vy = random(4, 5);//雨滴下落速度
this.hit = random(h * .8, h * .9);//下落的最大值
this.size = 2;//长方形宽度
},
draw:function(){
if (this.y < this.hit) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.size, this.size * 5);//绘制长方形，通过多次叠加长方形，形成雨滴下落效果
}
this.update();//更新位置
},
update:function(){
if(this.y < this.hit){
this.y += this.vy;//未达到底部，增加雨滴y坐标
}else{
this.init();
}
}
};

function resize(){
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
}

//初始化一个雨滴
var r = new RainDrop();
r.init();

function anim() {
ctx.fillStyle = clearColor;//每一帧都填充背景色
ctx.fillRect(0,0,w,h);//填充背景色，注意不要用clearRect，否则会清空前面的雨滴，导致不能产生叠加的效果
r.draw();//绘制雨滴
requestAnimationFrame(anim);//控制动画帧
}

window.addEventListener("resize", resize);
//启动动画
anim();
``````

### 涟漪效果

``````var c = document.getElementById("canvas-club");
var ctx = c.getContext("2d");//获取canvas上下文
var w = c.width = window.innerWidth;
var h = c.height = window.innerHeight;//设置canvas宽、高
var clearColor = 'rgba(0, 0, 0, .1)';//画板背景,注意最后的透明度0.1 这是产生叠加效果的基础

function random(min, max) {
return Math.random() * (max - min) + min;
}

function Rippling(){}
//涟漪对象 这是涟漪动画的主要部分
Rippling.prototype = {
init:function(){
this.x = random(0,w);//涟漪x坐标
this.y = random(h * .8, h * .9);//涟漪y坐标
this.w = 2;//椭圆形涟漪宽
this.h = 1;//椭圆涟漪高
this.vw = 3;//宽度增长速度
this.vh = 1;//高度增长速度
this.a = 1;//透明度
this.va = .96;//涟漪消失的渐变速度
},
draw:function(){
ctx.beginPath();
ctx.moveTo(this.x, this.y - this.h / 2);
//绘制右弧线
ctx.bezierCurveTo(
this.x + this.w / 2, this.y - this.h / 2,
this.x + this.w / 2, this.y + this.h / 2,
this.x, this.y + this.h / 2);
//绘制左弧线
ctx.bezierCurveTo(
this.x - this.w / 2, this.y + this.h / 2,
this.x - this.w / 2, this.y - this.h / 2,
this.x, this.y - this.h / 2);

ctx.strokeStyle = 'hsla(180, 100%, 50%, '+this.a+')';
ctx.stroke();
ctx.closePath();
this.update();//更新坐标
},
update:function(){
if(this.a > .03){
this.w += this.vw;//宽度增长
this.h += this.vh;//高度增长
if(this.w > 100){
this.a *= this.va;//当宽度超过100，涟漪逐渐变淡消失
this.vw *= .98;//宽度增长变缓慢
this.vh *= .98;//高度增长变缓慢
}
} else {
this.init();
}

}
};

function resize(){
w = c.width = window.innerWidth;
h = c.height = window.innerHeight;
}

//初始化一个涟漪
var r = new Rippling();
r.init();

function anim() {
ctx.fillStyle = clearColor;
ctx.fillRect(0,0,w,h);
r.draw();
requestAnimationFrame(anim);
}

window.addEventListener("resize", resize);
//启动动画
anim();
``````

### 总结

tinybear 赞了文章 · 2018-10-30

# Introduction to `RxJS`

## 1. 前言

### 1.1 什么是`RxJS`？

`RxJS``ReactiveX`编程理念的`JavaScript`版本。`ReactiveX`来自微软，它是一种针对异步数据流的编程。简单来说，它将一切数据，包括HTTP请求，DOM事件或者普通数据等包装成流的形式，然后用强大丰富的操作符对流进行处理，使你能以同步编程的方式处理异步数据，并组合不同的操作符来轻松优雅的实现你所需要的功能。

### 1.2 `RxJS`可用于生产吗？

`ReactiveX`由微软于2012年开源，目前各语言库由`ReactiveX`组织维护。`RxJS``GitHub`上已有`8782`个star，目前最新版本为`5.5.2`，并持续开发维护中，其中官方测试用例共计`2699`个。

### 1.3 `RxJS`对项目代码的影响？

`RxJS`中的流以`Observable`对象呈现，获取数据需要订阅`Observable`，形式如下：

``````const ob = http\$.getSomeList(); //getSomeList()返回某个由`Observable`包装后的http请求
ob.subscribe((data) => console.log(data));
//在变量末尾加\$表示Observable类型的对象。``````

``````const promise = http.getSomeList(); // 返回由`Promise`包装的http请求
promise.then((data) => console.log(data));``````

``````const ob = Observable.fromPromise(somePromise); // Promise转为Observable
const promise = someObservable.toPromise(); // Observable转为Promise``````

### 1.4 `RxJS`会增加多少体积？

`RxJS(v5)`整个库压缩后约为`140KB`，由于其模块化可扩展的设计，因此仅需导入所用到的类与操作符即可。导入`RxJS`常用类与操作符后，打包后的体积约增加`30-60KB`，具体取决于导入的数量。

`import { Observable } from 'rxjs/Observable' //导入类`
`import 'rxjs/add/operator/map' // 导入实例操作符 `
`import 'rxjs/add/observable/forkJoin' // 导入类操作符 `

## 2. `RxJS`快速入门

### 2.1 初级核心概念

• `Observable`
• `Observer`
• `Operator`

`Observable`被称为可观察序列，简单来说数据就在`Observable`中流动，你可以使用各种`operator`对流进行处理，例如：

``````const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 2);``````

``````const ob = Observable.interval(1000);
ob.take(3).map(n => n * 2).filter(n => n > 0).subscribe(n => console.log(n));``````

``````2 //第2秒
4 //第3秒``````

``````ob.subscribe({
next: d => console.log(d),
error: err => console.error(err),
complete: () => console.log('end of the stream')
})``````

`observer.next`可以认为是`Promise``then`的第一个参数，`observer.error`对应第二个参数或者`Promise``catch`

`RxJS`同样提供了`catch`操作符，`err`流入`catch`后，`catch`必须返回一个新的`Observable`。被`catch`后的错误流将不会进入`observer``error`函数，除非其返回的新`observable`出错。

``````Observable.of(1).map(n => n.undefinedMethod()).catch(err => {
// 此处处理catch之前发生的错误
return Observable.of(0); // 返回一个新的序列，该序列成为新的流。
});``````

### 2.2 创建可观察序列

#### `Observable.of(...args)`

`Observable.of()`可以将普通JavaScript数据转为可观察序列，点我测试

#### `Observable.fromPromise(promise)`

`Promise`转化为`Observable`点我测试

#### `Observable.fromEvent(elment, eventName)`

`DOM`事件创建序列，例如`Observable.fromEvent(\$input, 'click')`点我测试

### 2.3 合并序列

``````const ob1 = Observable.ajax('api/detail/1');
const ob2 = Observable.ajax('api/detail/2');
...
const obs = [ob1, ob2...];
// 分别创建对应的HTTP请求。``````
1. N个请求按顺序串行发出（前一个结束再发下一个）
``Observable.concat(...obs).subscribe(detail => console.log('每个请求都触发回调'));``
1. N个请求同时并行发出，对于每一个到达就触发一次回调
``Observable.merge(...obs).subscribe(detail => console.log('每个请求都触发回调'));``
1. N个请求同时发出并且要求全部到达后合并为数组，触发一次回调
``Observable.forkJoin(...obs).subscribe(detailArray => console.log('触发一次回调'));``

## 3. 使用`RxJS`实现搜索功能

``````<input id="text"></input>
<script>
var text = document.querySelector('#text');
text.addEventListener('keyup', (e) =>{
var searchText = e.target.value;
// 发送输入内容到后台
\$.ajax({
url: `/search/\${searchText}`,
success: data => {
// 拿到后台返回数据，并展示搜索结果
render(data);
}
});
});
</script>``````

• 多余的请求

• 已无用的请求仍然执行

``````<input id="text"></input>
<script>
var text = document.querySelector('#text'),
timer = null;
text.addEventListener('keyup', (e) =>{
// 在 250 毫秒内进行其他输入，则清除上一个定时器
clearTimeout(timer);
// 定时器，在 250 毫秒后触发
timer = setTimeout(() => {
console.log('发起请求..');
},250)
})
</script>``````

``````<input id="text"></input>
<script>
var text = document.querySelector('#text'),
timer = null,
currentSearch = '';

text.addEventListener('keyup', (e) =>{
clearTimeout(timer)
timer = setTimeout(() => {
// 声明一个当前所搜的状态变量
currentSearch ＝ '书';

var searchText = e.target.value;
\$.ajax({
url: `/search/\${searchText}`,
success: data => {
// 判断后台返回的标志与我们存的当前搜索变量是否一致
if (data.search === currentSearch) {
// 渲染展示
render(data);
} else {
// ..
}
}
});
},250)
})
</script>``````

``````var text = document.querySelector('#text');
var inputStream = Rx.Observable.fromEvent(text, 'keyup') //为dom元素绑定'keyup'事件
.debounceTime(250) // 防抖动
.pluck('target', 'value') // 取值
.switchMap(url => Http.get(url)) // 将当前输入流替换为http请求
.subscribe(data => render(data)); // 接收数据``````

`RxJS`能简化你的代码，它将与流有关的内部状态封装在流中，而不需要在流外定义各种变量来以一种上帝视角控制流程。`Rx`的编程方式使你的业务逻辑流程清晰，易维护，并显著减少出bug的概率。

### 个人总结的常用操作符：

`map`, `filter`,`switchMap`, `toPromise`, `catch`, `take`, `takeUntil`, `timeout`, `debounceTime`, `distinctUntilChanged`, `pluck`

tinybear 收藏了文章 · 2018-03-20

## centos安装使用puppeteer和headless chrome

Google推出了无图形界面的headless Chrome之后，可以直接在远程服务器上直接跑一些测试脚本或者爬虫脚本了，猴开心！Google还附送了Puppeteer用于驱动没头的Chome。

``````const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});

await browser.close();
})();``````

## 依赖安装

...node_modules/puppeteer/.local-chromium/linux-496140/chrome-linux/chrome: error while loading shared libraries: libpangocairo-1.0.so.0: cannot open shared object file: No such file or directory

TROUBLESHOOTING: https://github.com/GoogleChro...

``````#依赖库
yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y

#字体
yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc -y``````

## sandbox去沙箱

``````(node:30559) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Failed to connect to chrome!
(node:30559) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
``````

``````./chrome -v --no-sandbox --disable-setuid-sandbox

(chrome:5333): Gtk-WARNING **: cannot open display: ``````

``````const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({args: ['--no-sandbox', '--disable-setuid-sandbox']});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'example.png'});

await browser.close();
})();``````

tinybear 赞了文章 · 2018-03-07

## viewBox用来干嘛？

``````<circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>
``````

``````<svg style="width:150px; height:300px">
<circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>
</svg>
``````

``````<svg style="width:150px; height:300px" viewBox="0 0 400 400">
<circle cx="200" cy="200" r="200" fill="#fdd" stroke="none"></circle>
</svg>``````

viewBox的四个参数分别代表：最小X轴数值；最小y轴数值；宽度；高度。

svg有个特点，在默认情况下，会调整这个viewBox的大小，让这个viewBox正好能被放进svg里去。拿上面例子来说，viewBox是个正方形，而svg的宽度比高度小，所以就把viewBox的大小缩小到和svg宽度一样，就能正好将viewBox放进svg里来了。所以现在viewBox的实际大小是个150px*150px的正方形。

## preserveAspectRatio又用来干嘛？

``<svg style="width:150px; height:300px" viewBox="0 0 400 400" preserveAspectRatio="xMidYMid meet">``

``````xMinYMin,
xMinYMid,
xMinYMax,
xMidYMin,
xMidYMid,
xMidYMax,
xMaxYMin,
xMaxYMid,
xMaxYMax``````

meet就是前面那种自动调整viewBox到可以在svg画布中完全展示。非常类似css里background-size:contain

preserveAspectRatio还有个单独使用的参数："none"。

tinybear 提出了问题 · 2018-02-28

## node的gm模块调用drawText绘制的文本模糊

``````    gm(100, 100, "#000000")
.font('./华文黑体.ttf')
.fontSize(12)
.stroke('#ffffff', 1)
.drawText(10, 50, "一二三高兴，奇怪")
.write("./brandNewImg.png", function (err) {
console.log('end')
});``````

tinybear 赞了文章 · 2018-01-05

## 配置开发调试环境

### 安装浏览器

• Chrome: 打开 `chrome://flags/#enable-webassembly`，选择 `enable`

• Firefox: 打开 `about:config``javascript.options.wasm` 设置为 `true`

## 快速体验 WebAssembly

``````WebAssembly.compile(new Uint8Array(`
00 61 73 6d  01 00 00 00  01 0c 02 60  02 7f 7f 01
7f 60 01 7f  01 7f 03 03  02 00 01 07  10 02 03 61
64 64 00 00  06 73 71 75  61 72 65 00  01 0a 13 02
08 00 20 00  20 01 6a 0f  0b 08 00 20  00 20 00 6c
0f 0b`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
const instance = new WebAssembly.Instance(module)
const { add, square } = instance.exports

console.log('2 + 4 =', add(2, 4))
console.log('3^2 =', square(3))
console.log('(2 + 5)^2 =', square(add(2 + 5)))

})``````

### 运行结果

``````2 + 4 = 6
3^2 = 9
(2 + 5)^2 = 49``````

### 解释代码

WebAssembly 提供了 JS API，其中 `WebAssembly.compile` 可以用来编译 wasm 的二进制源码，它接受 BufferSource 格式的参数，返回一个 Promise。

``````new Uint8Array(
`...`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)``````

### 注意数据类型

WebAssembly 是有明确的数据类型的，我那个例子里用的都是 32 位整型数（是不是看不出来…… 二进制里那些 `7f` 表示 `i32` 指令，意思就是32位整数），所以用 WebAssembly 编译出来的时候要注意数据类型。

``````console.log(square('Tom')) // 0
console.log(add(2e+66, 3e+66)) // 0
console.log(2e+66 + 3e+66) // 5e+66``````

## 把 C/C++ 编译成 WebAssembly

``````         Emscripten
source.c   ----->  target.js

Emscripten (with flag)
source.c   ----->  target.wasm``````

### 编写 C 代码

``````// math.c

int add (int x, int y) {
return x + y;
}

int square (int x) {
return x * x;
}``````

### 代码解释

C 语言代码一目了然，就是写了两个函数，由于 C 语言里的函数都是全局的，这两个函数默认都会被模块导出。

## 如何运行 WebAssembly 二进制文件？

WebAssembly 目前只设计也只实现了 javascript API，就像我刚开始提供的那个例子一样，只有通过 js 代码来编译、实例化才可以调用其中的接口。这也很好的说明了 WebAssembly 并不是要替代 javascript ，而是用来增强 javascript 和 Web 平台的能力的。我觉得 WebAssembly 更适合用于写模块，承接各种复杂的计算，如图像处理、3D运算、语音识别、视音频编码解码这种工作，主体程序还是要用 javascript 来写的。

### 编写加载函数 (loader)

``````function loadWebAssembly (path) {
return fetch(path)                   // 加载文件
.then(res => res.arrayBuffer())    // 转成 ArrayBuffer
.then(WebAssembly.instantiate)     // 编译 + 实例化
.then(mod => mod.instance)         // 提取生成都模块
}``````

``````loadWebAssembly('path/to/math.wasm')
.then(instance => {
const { add, square } = instance.exports
// ...
})``````

### 更完整的加载函数

``````/**
* @param {String} path wasm 文件路径
* @param {Object} imports 传递到 wasm 代码中的变量
*/
function loadWebAssembly (path, imports = {}) {
return fetch(path)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports.env = imports.env || {}

// 开辟内存空间
imports.env.memoryBase = imports.env.memoryBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}

// 创建变量映射表
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.table) {
// 在 MVP 版本中 element 只能是 "anyfunc"
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}

// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)
})
}``````

### 调用 wasm 导出的接口

``````loadWebAssembly('./math.wasm')
.then(instance => {
const add = instance.exports._add
const square = instance.exports._square

console.log('2 + 4 =', add(2, 4))
console.log('3^2 =', square(3))
console.log('(2 + 5)^2 =', square(add(2 + 5)))
})``````

## 把 asm.js 编译成 WebAssembly

asm.js 是 javascript 的子集，是一种语法（不是一个前端工具库！），用了很多底层语法来标注数据类型，目的是提高 javascript 的运行效率，本身就是作为 C/C++ 编译的目标设计的（不是给人写的），可以理解为一种中间表示层语法 (IR, Intermediate Representation)。asm.js 出生于 WebAssembly 之前， WebAssembly 借鉴了这个思路，做的更彻底一些，直接跳过 javascript ，设计了一套新的平台指令。

### 编写 asm.js 代码

``````// math.js

function () {
"use asm";

function add (x, y) {
x = x | 0;
y = y | 0;
return x + y | 0;
}

function square (x) {
x = x | 0;
return x * x | 0;
}

return {
add: add,
square: square
};
}``````

### 使用 Binaryen 和 WABT

``````        Binaryen             WABT
math.js   --->   math.wast   --->   math.wasm``````

``````asm2wasm math.js -o math.wast
wast2wasm math.wast -o math.wasm``````

### wast 是什么格式？

WebAssembly 除了定义了二进制格式以外，还定义了一份对等的文本描述。官方给出的是线性表示的例子，而 wast 是用 S-表达式(s-expressions) 描述的另一种文本格式。

``````(module
(export "add" (func \$add))
(export "square" (func \$square))

(func \$add (param \$x i32) (param \$y i32) (result i32)
(return
(i32.add
(get_local \$x)
(get_local \$y)
)
)
)

(func \$square (param \$x i32) (result i32)
(return
(i32.mul
(get_local \$x)
(get_local \$x)
)
)
)
)``````

## 在 WebAssembly 中调用 Web API

### 向 wasm 中传递 js 变量

``````const imports = {
Math,
objects: {
count: 2333
},
methods: {
output (message) {
console.log(`-----> \${message} <-----`)
}
}
}

loadWebAssembly('path/to/source.wasm', imports)
.then(instance => {
// ...
})``````

``new WebAssembly.Instance(module, imports)``

### 获取并使用从 js 传递的变量

``````(module
(import "objects" "count" (global \$count f32))
(import "methods" "output" (func \$output (param f32)))
(import "Math" "sin" (func \$sin (param f32) (result f32)))

(export "test" (func \$test))
(func \$test (param \$x f32)
(call \$output (f32.const 42))
(call \$output (get_global \$count))
(call \$output (get_local \$x))
(call \$output
(call \$sin
(get_local \$x)
)
)
)
)``````

``(import "objects" "count" (global \$count f32))``

``(import "methods" "output" (func \$output (param f32)))``

``(import "Math" "sin" (func \$sin (param f32) (result f32)))``

### 调用 js 中定义的接口

``````  (func \$test (param \$x f32)
(call \$output (f32.const 42))
(call \$output (get_global \$count))
(call \$output (get_local \$x))
(call \$output
(call \$sin
(get_local \$x)
)
)
)``````

### 编译执行

``````loadWebAssembly('path/to/source.wasm', imports)
.then(instance => {
const { test } = instance.exports
test(2333)
})``````

``````-----> 42 <-----
-----> 666 <-----
-----> 2333 <-----
-----> 0.9332447648048401 <-----``````

## 结语

tinybear 赞了回答 · 2017-12-25

## 解决当在Webpack里配置了别名之后，Webstorm里可以设置路径提示吗

windows：

``````File > Settings > Languages and Frameworks >JavaScript | Webpack
``````

macOS：

``````WebStorm | Preferences | Languages and Frameworks | JavaScript | Webpack
``````

tinybear 赞了文章 · 2017-07-05

## 简单方案

### redux-thunk：指路先驱

Redux作者Dan写的中间件，因官方文档出镜而广为人知。

Redux本身只能处理同步的Action，但可以通过中间件来拦截处理其它类型的action，比如函数(Thunk)，再用回调触发普通Action，从而实现异步处理，在这点上所有Redux的异步方案都是类似的

``````//action types
const GET_DATA = 'GET_DATA',
GET_DATA_SUCCESS = 'GET_DATA_SUCCESS',
GET_DATA_FAILED = 'GET_DATA_FAILED';

//action creator
const getDataAction = function(id) {
return function(dispatch, getState) {
dispatch({
type: GET_DATA,
payload: id
})
api.getData(id) //注：本文所有示例的api.getData都返回promise对象
.then(response => {
dispatch({
type: GET_DATA_SUCCESS,
payload: response
})
})
.catch(error => {
dispatch({
type: GET_DATA_FAILED,
payload: error
})
})
}
}

//reducer
const reducer = function(oldState, action) {
switch(action.type) {
case GET_DATA :
return oldState;
case GET_DATA_SUCCESS :
return successState;
case GET_DATA_FAILED :
return errorState;
}
}``````

### redux-promise：瘦身过头

• 若resolve，触发一个此action的拷贝，但payload为promise的value，并设status属性为"success"

• 若reject，触发一个此action的拷贝，但payload为promise的reason，并设status属性为"error"

``````//action types
const GET_DATA = 'GET_DATA';

//action creator
const getData = function(id) {
return {
type: GET_DATA,
payload: api.getData(id) //payload为promise对象
}
}

//reducer
function reducer(oldState, action) {
switch(action.type) {
case GET_DATA:
if (action.status === 'success') {
return successState
} else {
return errorState
}
}
}``````

redux-promise为了精简而做出的妥协非常明显：无法处理乐观更新

### redux-promise-middleware：拔乱反正

redux-promise-middleware相比redux-promise，采取了更为温和和渐进式的思路，保留了和redux-thunk类似的三个action。

``````//action types
const GET_DATA = 'GET_DATA',
GET_DATA_PENDING = 'GET_DATA_PENDING',
GET_DATA_FULFILLED = 'GET_DATA_FULFILLED',
GET_DATA_REJECTED = 'GET_DATA_REJECTED';

//action creator
const getData = function(id) {
return {
type: GET_DATA,
payload: {
promise: api.getData(id),
data: id
}
}
}

//reducer
const reducer = function(oldState, action) {
switch(action.type) {
case GET_DATA_PENDING :
return oldState; // 可通过action.payload.data获取id
case GET_DATA_FULFILLED :
return successState;
case GET_DATA_REJECTED :
return errorState;
}
}
``````

``````const getData = function(id) {
return {
type: GET_DATA,
payload: api.getData(id) //等价于 {promise: api.getData(id)}
}
}``````

### redux-action-tools：软文预警

redux-action-tools是我给出的答案：

``````const GET_DATA = 'GET_DATA';

//action creator
const getData = createAsyncAction(GET_DATA, function(id) {
return api.getData(id)
})

//reducer
const reducer = createReducer()
.when(getData, (oldState, action) => oldState)
.done((oldState, action) => successState)
.failed((oldState, action) => errorState)
.build()``````

redux-action-tools在action层面做的事情与前面几个库大同小异：同样是派发了三个action：`GET_DATA`/`GET_DATA_SUCCESS`/`GET_DATA_FAILED`。这三个action的描述见下表：

typeWhenpayloadmeta.asyncPhase
`\${actionName}`异步开始前同步调用参数'START'
`\${actionName}_COMPLETED`异步成功value of promise'COMPLETED'
`\${actionName}_FAILED`异步失败reason of promise'FAILED'

`createAsyncAction`参考了redux-promise作者写的redux-actions ，它接收三个参数，分别是：

1. actionName 字符串，所有派生action的名字都以它为基础，初始action则与它同名

2. promiseCreator 函数，必须返回一个promise对象

3. metaCreator 函数可选，作用后面会演示到

#### 场景解析：失败处理与Loading

1. 底层处理，扩展性不足

``````function fetchWrapper(args) {
return fetch.apply(fetch, args)
.catch(commonErrorHandler)
}``````

在较底层封装ajax库可以轻松实现全局处理，但问题也非常明显：

一是扩展性不足，比如少数场景想要绕过通用处理逻辑，还有一些场景错误是前端生成而非直接来自于请求；

二是不易组合，比如有的场景一个action需要多个异步请求，但异常处理和loading是不需要重复的，因为用户不需要知道一个动作有多少个请求。

2. 不够内聚，侵入业务代码

``````//action creator
const getData = createAsyncAction(GET_DATA, function(id) {
return api.getData(id)
.catch(commonErrorHandler) //调用错误处理函数
})``````

在有业务意义的action层调用通用处理逻辑，既能按需调用，又不妨碍异步请求的组合。但由于通用处理往往适用于多数场景，这样写会导致业务代码变得冗余，因为几乎每个action都得这么写。

3. 高耦合，高风险

也有人把上面的方案做个依赖反转，改为在通用逻辑里监听业务action：

``````function commonErrorReducer(oldState, action) {
switch(action.type) {
case GET_DATA_FAILED:
case PUT_DATA_FAILED:
//... tons of action type
return commonErrorHandler(action)
}
}``````

这样做的本质是把冗余从业务代码中拿出来集中管理。

问题在于每添加一个请求，都需要修改公共代码，把对应的action type加进来。且不说并行开发时merge冲突，如果加了一个异步action，但忘了往公共处理文件中添加——这是很可能会发生的——而异常是分支流程不容易被测试发现，等到发现，很可能就是事故而不是bug了。

• 面向异步动作(action)，而非直接面向请求

• 不侵入业务代码

• 默认使用通用处理逻辑，无需额外代码

• 可以绕过通用逻辑

``````import _ from 'lodash'
import { ASYNC_PHASES } from 'redux-action-tools'

function errorMiddleWare({dispatch}) {
return next => action => {
const asyncStep = _.get(action, 'meta.asyncStep');

if (asyncStep === ASYNC_PHASES.FAILED) {
dispatch({
type: 'COMMON_ERROR',
payload: {
action
}
})
}

next(action);
}
}``````

``````import _ from 'lodash'
import { ASYNC_PHASES } from 'redux-action-tools'

const customizedAction = createAsyncAction(
type,
promiseCreator, //type 和 promiseCreator此处无不同故省略
(payload, defaultMeta) => {
return { ...defaultMeta, omitError: true }; //向meta中添加配置参数
}
)

function errorMiddleWare({dispatch}) {
return next => action => {
const asyncStep = _.get(action, 'meta.asyncStep');
const omitError = _.get(action, 'meta.omitError'); //获取配置参数

if (!omitError && asyncStep === ASYNC_PHASES.FAILED) {
dispatch({
type: 'COMMON_ERROR',
payload: {
action
}
})
}

next(action);
}
}``````

## 进阶方案

### redux-loop：分形! 组合!

``````import { Effects, loop } from 'redux-loop';
import { loadingStart, loadingSuccess, loadingFailure } from './actions';

export function fetchDetails(id) {
return fetch(`/api/details/\${id}`)
.then((r) => r.json())
.then(loadingSuccess)
.catch(loadingFailure);
}

export default function reducer(state, action) {
switch (action.type) {
case 'LOADING_START':
return loop(
{ ...state, loading: true },
Effects.promise(fetchDetails, action.payload.id)
); // 同时返回状态与副作用

case 'LOADING_SUCCESS':
return {
...state,
loading: false,
details: action.payload
};

case 'LOADING_FAILURE':
return {
...state,
loading: false,
error: action.payload.message
};

default:
return state;
}
}``````

### redux-saga：难、而美

``````import { takeEvery } from 'redux-saga'
import { call, put } from 'redux-saga/effects'
import Api from '...'

function* getData(action) {
try {
const response = yield call(api.getData, action.payload.id);
yield put({type: "GET_DATA_SUCCEEDED", payload: response});
} catch (e) {
yield put({type: "GET_DATA_FAILED", payload: error});
}
}

function* mySaga() {
yield* takeEvery("GET_DATA", getData);
}

export default mySaga;``````

#### 场景解析：竞态

``````function fetchFriend(id){
return (dispatch, getState) => {
//步骤1：在reducer中 set state.currentFriend = id;
dispatch({type: 'FETCH_FIREND', payload: id});

return fetch(`http://localhost/api/firend/\${id}`)
.then(response => response.json())
.then(json => {
//步骤2：只处理currentFriend的对应response
const { currentFriend } = getState();
(currentFriend === id) && dispatch({type: 'RECEIVE_FIRENDS', playload: json})
});
}
}
``````

``````import { takeLatest } from `redux-saga`

function* fetchFriend(action) {
...
}

function* watchLastFetchUser() {
yield takeLatest('FETCH_FIREND', fetchFriend)
}``````

## 小结

tinybear 关注了标签 · 2017-02-09

## 机器学习

#### 认证与成就

• 获得 53 次点赞
• 获得 25 枚徽章 获得 0 枚金徽章, 获得 9 枚银徽章, 获得 16 枚铜徽章

(ﾟ∀ﾟ　)