0%

Promise 三种状态pending,rejected,fulfilled,只有异步操作的结果能够决定当前的状态,并且状态一旦改变,不能修改。

一般来说,调用resolve或reject以后,Promise 的使命就完成了,后继操作应该放到then方法里面,而不应该直接写在resolve或reject的后面。所以,最好在它们前面加上return语句,这样就不会有意外

1
2
3
4
5
6
7
8
9
 // 立即resolve的Promise在本轮事件循环的末尾执行的。

new Promise((resolve, reject) => {
console.log(1);
return resolve(2);
console.log(3);
}).then((res) => {
console.log(res);
});

then方法返回一个新的Promise,返回的参数作为下一个then方法的函数参数,如果返回的是一个Promise,下一个then会等待Promise状态确定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
new Promise((resolve, reject) => {
resolve(new Promise((resolve1) => {
setTimeout(() => {
resolve1('resolve 1000');
}, 1000);
}));
}).then((res) => {
console.log(res);
return new Promise((resolve1) => {
setTimeout(() => {
resolve1('resolve 2000');
}, 2000);
});
}).then(res => {
console.log(res);
});

Promise.resolve

Promise.resolve 有四种情况

  • 传入Promise,不做任何处理
  • 传入包含then方法的对象
  • 传入原始值或不包含then方法的对象
  • 不传任何参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

// Promise.resolve

var pp1 = Promise.resolve({ a: '1' });
pp1.then(res => {
console.log(res);
});
// 会调用对象的then方法,结果 先打印hahaah 在打印Promise.resolve+then,这是因为立即resolve的Promise会在本次事件循环末尾调用
var pp2 = Promise.resolve({
a: '2', then(resolve) {
resolve('Promise.resolve+then');
console.log('hahaah');
}
});
pp2.then(res => {
console.log(res);
});

// 如果没有then方法,会直接传入then 方法的回调函数的参数

Promise.try

不管是同步还是异步操作,Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。

1
2
3
4
5
6
7
8

// Promise.try()
// 实现方法
Promise.try = function (func) {
return new this(function (resolve) {
resolve(func());
});
};

错误处理

promise错误会一直向后传递,如果没有指定catch方法的话,Promise对象抛出的错误不会传递到外层代码。

Promise.all或Promise.race单个Promise没有调用自己的的catch方法,那么会被 Promise.all().catch() 或 Promise.race().catch() 捕获错误。

实现自己的Promise

首先 Promises/A+规范标准 官网地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
function MyPromise(executor) {
var self = this;
self.status = 'pending';
self.data = null;
self.onResolvedCallback = [];
self.onRejectdCallback = [];

function resolve(value) {
if (value instanceof MyPromise) return value.then(resolve, reject);
// 保证立即调用的resolve后面then方法添加的函数能够执行
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'fulfilled';
self.data = value;
for (var i = 0, len = self.onResolvedCallback.length; i < len; i++) {
self.onResolvedCallback[i](value);
}
}
}, 0);
}

function reject(reason) {
setTimeout(() => {
if (self.status === 'pending') {
self.status = 'reject';
self.data = reason;
for (var i = 0, len = self.onRejectdCallback.length; i < len; i++) {
self.onRejectdCallback[i](reason);
}
}
}, 0);

}

try {
executor(resolve, reject);
} catch (e) {
reject(e);
}

}

function resolvePromise(promise2, x, resolve, reject) {
var then;
var thenCalledOrThrow = false; // 保证 resolve ,reject 只执行一次

if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'));
}

if (x instanceof MyPromise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function (v) {
resolvePromise(promise2, v, resolve, reject);
}, reject);
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject);
}
return;
}

if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then; //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return resolvePromise(promise2, y, resolve, reject);
}, function rj(r) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return reject(r);
});
} else {
resolve(x);
}
} catch (e) {
if (thenCalledOrThrow) return;
thenCalledOrThrow = true;
return reject(e);
}
} else {
resolve(x);
}
}

MyPromise.prototype.then = function (onResolve, onRejectd) {
var self = this;
var promise2;
onResolve = typeof onResolve === 'function' ? onResolve : function (value) {
/* 解决值的穿透问题 MyPromise().then().then().then(res => {
console.log(res);
});*/
return value;
};
onRejectd = typeof onRejectd === 'function' ? onRejectd : function (reason) {
throw reason;
};

if (self.status === 'fulfilled') {
return promise2 = new MyPromise(function (resolve, reject) {
// 异步调用
setTimeout(() => {
try {
var x = onResolve(self.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
});
}
if (self.status === 'reject') {
return promise2 = new MyPromise(function (resolve, reject) {
setTimeout(() => {
try {
var x = onRejectd(self.data);
resolvePromise(promise2, x, resolve, reject);
} catch (reason) {
reject(reason);
}
});
});
}

// 当pending时,不能确定调用 onResolve,还是 onRejectd
if (self.status === 'pending') {
return promise2 = new MyPromise(function (resolve, reject) {
self.onResolvedCallback.push(function (value) {
try {
var x = onResolve(value);
resolvePromise(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});

self.onRejectdCallback.push(function (reason) {
try {
var x = onRejectd(reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
};

MyPromise.prototype.catch = function (onRejectd) {
return this.then(null, onRejectd);
};

测试 promise

1
2
npm i -g promises-aplus-tests
promises-aplus-tests Promise.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 将b的方法复制到a
function merge (a, b){
if (a && b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
};


// 链式调用
function My() {
this.sum = 0;
}

My.prototype.add = function (a, b) {
this.sum = a + b;
return this;
};

My.prototype.sub = function (b, c) {
this.sum -= b + c;
return this;
};
var my = new My();
console.log(my.add(1, 6).sub(3, 4).sum);


// 不调用new 实例化

function Person(options) {
if(!(this instanceof Person)){
return new Person(options)
}
}

中间件的使用案例

1
2
3
4
5
6
const express = require('express');
const app = express();

app.use(function(req,res,next) {
next()
})

以上是最简单的例子。一个中间件就是一个函数,参数为req,res,next。在这个函数中,必须执行next函数或者发送响应,否者服务会被挂起,客户端会一直处于等待。

为什么要使用中间件

在中间件中可以处理req、res对象,给他们增加新的方法和属性,供下一个中间件调用,还可以通过next控制权限,提前中断后续中间件的调用。这样的话就可以将应用的功能拆分出来,实现功能的解耦。

中间件的原理是什么

connect内部维护了一个数组,执行app.use会往这个数组中增加一个中间件。当接收到一个请求时,如果中间件执行了next(),会依次往下执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
var http = require('http');

function express() {
var stack = []; // 中间件函数数组

function app(req, res) {
var i = 0;

function next() {
var handler = stack[i++];

if (!handler) {
return;
}
// 中间件函数调用,如果调用了next,相当于递归调用,下一个中间件函数就会执行
handler(req, res, next);

}

next();
}

// 增加中间件
app.use = function (middleware) {
stack.push(middleware);
};

return app;
}

var app = express();
http.createServer(app).listen(3000);

app.use(function (req, res, next) {
console.log('start1');
next();
console.log('next1');
});
app.use(function (req, res, next) {
console.log('start2');
next();
console.log('next2');
});
app.use(function (req, res) {
console.log('start3');
});
// 打印结果
// start1
// start2
// start3
// next2
// next1

注意,这里用了两个闭包,一个维护stack中间件数组,一个维护中间件函数下标i
前面看的是express3,后面看了express4源码。

express 不使用 connect 管理中间件了。
一个中间件包含一个 route 属性,当一个请求进来时,循环所有的中间件,next(router) 提前退出,没有定义路由的提前退出,路径不匹配的提前退出。

Routestack 数组,里面都是 Layer 实例,在解析中间件时,Layer 将路由路径转化为正则表达式,并将参数解析,放到他自己本身的属性上,在循环中间件时,req 对象上合并 Layerparams 属性。在Route.process_params回调中调用 Layer.handle_request 执行中间件函数。

进程和线程

了解进程和线程,更好的理解js单线程。

官方术语

进程是cpu资源(包括内存等)分配的最小单元(是能拥有资源和独立运行的最小单元)。

线程是cpu调度的最小单元。

cpu资源包括什么??
这里的调度的意思是什么??

网络上看到很多次的比喻:

将进程比作一个工厂,工厂和工厂直接互不影响。工厂里面有很多工人,工人相当于线程,工厂里面的电力等资源工人都可以使用,一个或多个工人合作完成任务。

从上面可以得出:

  • 进程之间互不影响
  • 一个进程包含一个或多个线程
  • 同一个进程中的线程共享资源

浏览器的进程和线程

首先浏览器是多进程的,可以打开谷歌浏览器的任务管理器查看。基本上一个标签页就是一个进程,一个插件也是一个进程,但是多个空白标签页合并为一个进程,浏览器做了优化。多进程的好处是什么呢?当一个标签页或者插件崩溃了,并不会影响到其他的进程,如果是单进程的话,只要有一个崩溃的话会影响整个浏览器。多进程充分利用多核优势。

多进程为什么可以充分利用多核优势??

浏览器包含的进程

Browser进程:浏览器的主进程,只要一个,负责主控和协调。主要的作用跟整个浏览器相关的:

  • 浏览器的界面与用户的交互
  • 负责各个页面的管理,创建和销毁其他进程
  • 网络资源的管理和下载
  • 负责将Renderer进程内存里的Bitmap绘制成界面 o(╥﹏╥)o不懂啥意思

第三方插件进程:这个比较容易理解,一个插件一个进程,仅当插件启用时才会被创建。

GPU进程:最多一个,用于3D绘制。

什么是GPU??

Renderer进程:浏览器渲染进程,默认每个Tab页面一个进程,互不影响。 Renderer进程是多线程的,主要作用为页面渲染、脚本执行、事件处理等。

Renderer进程包含的线程

Renderer进程是前端关注的重点,主要包括GUI渲染线程、JS引擎线程、事件触发线程、定时器触发线程、ajax网络请求线程等。

GUI渲染线程主要负责:

  • HTML、CSS解析,构建DOM树和RenderObject树,布局和绘制
  • 当发生Repaint或者reflow时,该线程就是执行
  • GUI渲染线程和JS引擎线程是互斥的,这两个线程同时只有一个线程运行

JS引擎线程主要作用:

  • 负责执行javascript脚本
  • 等待任务队列的任务触发,一个Renderer进程只有一个JS线程在运行JS程序。
  • JS引擎线程和GUI渲染线程是互斥的

事件触发线程主要作用:

  • 用来控制事件循环
  • 当JS引擎执行setTimeout、发送Ajax,绑定事件响应等,会将对应的任务添加到该线程
  • 当对应的事件符合触发条件时,该线程会把事件添加到任务队列的末尾,等待JS引擎空闲时执行。

定时器触发线程:

  • setTimeout 和setInterval所在线程
  • 因为JS是单线程的,如果处于阻塞线程状态,会影响计时的准确性,所以另开线程。
  • 计时完毕后,会将事件添加到任务队列的末尾,等待JS引擎空闲时执行。

ajax网络请求线程:

  • 在XMLHttpRequest请求连接成功后,浏览器新开一个线程请求
  • 在状态请求状态变更时,会产生状态变更事件,将回调函数放在任务队列的末尾,等待JS引擎空闲时执行。

js为什么是单线程

其实这与它的用途有关。作为浏览器脚本语言,JavaScript 的主要用途是与用户互动,以及操作 DOM。若以多线程的方式操作这些 DOM,则可能出现操作的冲突。假设有两个线程同时操作一个 DOM 元素,线程 1 要求浏览器删除 DOM,而线程 2 却要求修改 DOM 样式,这时浏览器就无法决定采用哪个线程的操作。当然,我们可以为浏览器引入“锁”的机制来解决这些冲突,但这会大大提高复杂性,所以 JavaScript 从诞生开始就选择了单线程执行。

setTimeout 只是将事件插入到task queue,必须等到execution context stack(执行栈) 执行完成,
主线程才会去执行指定的回调函数。所以当程序耗时很长时,并不能保证setTimeout能够在指定时间执行。比如:

1
2
3
4
5
6
setTimeout(()=>{
console.log('start');
},0)
for (let i = 0; i < 100000; i++) {
console.log(i);
}

并不能保证在0 ms 之后打印 start并且setTimeout最短间隔不得低于 4ms,低于这个值会自动增加。
setTimeout(fn,0)的意思是让fn在主线程最早有空闲的时候执行。
vue中,数据的改变并不会马上触发Dom元素的更新,需要使用$nextTick或者setTimeout(fn,0),保证可以获取到更新后的DOM元素

nodejs中的event-loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
console.log(1);
setTimeout(() => {
console.log(2);
}, 0);
console.log(4);
process.nextTick(() => {
console.log(5);
process.nextTick(() => {
console.log(6);
});
});
setTimeout(() => {
console.log(7);
}, 0);
setImmediate(() => {
console.log(8);
}, 0);

开发使用jetbrain开发工具,经常会改动配置文件,比如:修改快捷键、代码段、文件模板等,需要同步公司和个人电脑的配置。最开始使用的是Dropbox同步配置文件,每次改动都需要导出,然后每个编辑器再导入,非常麻烦,效率很低。今天发现webstrom等自动设置同步插件,只有设置一个同步仓库,每次IDE启动、退出时默认同步配置,非常的方便。

这个插件的名字 settings repository,一般自带这个插件,没有的话到插件中心下载就好了。
这个是详细的介绍地址 Settings Repository