Javascript进阶笔记

Promise

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const promise = new Promise(
(resolve,reject) => {
if(/*异步操作*/) {
resolve(result);
} else {
reject(error);
}
}
);
promise.then(
(res) => {
//成功操作
}
).catch(
(error) => {
//错误操作
}
);

初始化Promise对象时需要传入一个函数,函数内部用来处理异步操作。该函数内置了两个参数resolve和reject。resolve则将padding状态变为fulfilled,进入下一个then。而reject则将状态变为reject,进入下一个catch。

被吃掉的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
const someAsyncThing = function() {
return new Promise(
(resolve,reject) => {
resolve(x); // ReferenceError
}
);
};
someAsyncThing().then(function() {
console.log('everything is great');
});
setTimeout(() => { console.log(123) }, 2000);

Promise内的函数new出来的时候会立即执行,尽管Promise内的函数发生了错误,但是不会终止脚本的执行,会等到脚本结束时,错误信息随其他输出一并输出。因而最好使用catch方法捕获错误。

不能被吃掉的错误

1
2
3
4
5
6
7
8
9
function p1(){
return new Promise((resolve,reject) => {
x //ReferenceError
});
}
p1().catch((e) => {
console.log(e.message); //x is not defined
});

对于Promise内部非异步语句,Promise都能吃掉,如果有catch方法的话能被catch方法捕获到,不过遇到以下情况的话:

1
2
3
4
5
6
7
8
9
function p1(){
return new Promise((resolve,reject) => {
setTimeout(() => {throw new Error('xxx')},2000); //无法捕获
});
}
p1().catch((e) => {
console.log(e.message);
});

这种情况将无法捕获,因此有延时函数在,此时延时函数的回调执行将跟Promise不在同一轮,轮到回调执行时,Promise已经运行完,此时的异常为Promise外的异常。

遇到这种情况的异常应该异步的回调函数内部使用try,catch捕获,然后catch内可以使用reject方法将错误抛给Promise的catch。

catch之后可以继续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const someAsyncThing = function() {
return new Promise(
(resolve,reject) => {
resolve(x); // ReferenceError
}
);
};
someAsyncThing().catch(
(e) => {
console.log("error");
return "from-catch"
}
).then(
(str)=>{
console.log(str);
}
);
//output: error from-catch

Promise.prototype.finally()

1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

finally回调函数不接受任何参数,因此finally的执行与Promise的执行状态无关,一旦前面操作完成后,必定执行。

Promise.all()

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
function p1(){
return new Promise((resolve) => {
setTimeout(()=>{resolve(1)},5000);
});
}
function p2(){
return new Promise(
(resolve) => {
resolve(2)
});
}
function p3(){
return new Promise(
(resolve) => {
resolve(3)
});
}
const p = Promise.all([p1(),p2(),p3()]);
p.then(
([r1,r2,r3]) => {
console.log("success"+r1+r2+r3);
}
).catch(
(e) => {
console.log(e.name+":"+e.message);
}
);

all方法,当数组内的Promise对象的状态都是fulfilled时,p的状态才是fulfilled。否则,当中有一个为reject时,p的状态将设置为reject,然后将第一被reject的实例传给catch,后面的实例虽然仍继续运行,但是尽管reject或者抛出异常也不会传给catch方法。

Promise.race()

1
const p = Promise.race([p1, p2, p3]);

只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

将多个异步变为同步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const p = new Promise(
(resolve,reject) => {
setTimeout(()=>{console.log("start");resolve();},1000);
}
);
p.then(() => {
return new Promise(
(resolve,reject) => {
setTimeout(()=>{console.log("1");resolve();},4000);
}
);
}).then(() => {
return new Promise(
(resolve,reject) => {
setTimeout(()=>{console.log("2");resolve();},1000);
}
);
});

以上将会将3个异步操作按同步的方式依次执行。此方法若没有业务需求,尽量少用。

async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function asyncFunc() {
return new Promise(
(resolve,reject) => {
setTimeout(()=>{resolve("result");},2000);
}
);
}
async function exec(){
const result = await asyncFunc(); //阻塞后面的操作
console.log(result);
}
exec();

async表示这里有异步操作,await表示需要等待后面异步操作结果的获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function asyncFunc() {
return new Promise(
(resolve,reject) => {
setTimeout(()=>{resolve("result");},2000);
}
);
}
async function exec(){
const result = await asyncFunc();
return result;
}
exec().then((res)=>{
console.log(res); // result
});

async函数返回一个Promise包装的对象,因此可以使用then进行后续操作。

函数节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function throttle(func,duration) {
var saveFunc = func;
var timmer;
var firstTime = true;
return function() {
var args = arguments;
var _this = this;
if (firstTime) { // 首次调用
saveFunc.apply(_this,args);
}
if (timmer) return false; // 仍在执行前的计时
timer = setTimeout(function() {
clearTimeout(time); // 清除定时器
timer = null;
saveFunc(_this,args);
},duration);
}
}

降低func函数的频率。func函数除了第一次执行以外,每次调用的有一定的间隔。

函数防抖

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
function debounce(func,wait,immediate) {
var timer,args,context,triggerTime,result;
var later = function() {
var last = new Date().getTime() - triggerTime;
if(last<wait && last>=0) {
timer = setTimeout(later,wait-last);
} else {
timer = null;
if(!immediate) {
result = func.apply(context,args);
if(!timer) context = args = null;
}
}
}
return function() {
context = this;
args = arguments;
triggerTime = new Date().getTime();
var callNow = immediate && !timer;
if(!timer) timer = setTimeout(later,wait);
if(callNow) {
result = func.appply(context,args);
context = args = null;
}
return result;
};
}

函数防抖即在离最近触发要经过一段时间才能生效,否则每次触发都会重置计时器,重新算下一次生效时间。wait参数为规定的固定时间,immediate为true时表示前缘触发(生效条件成立时一触发就生效),为false为后缘触发(生效条件成立后经过固定时间才生效)。

每当调用函数内的闭包时,就会更新触发时间。later延时执行,每当到执行时间,就会检查是否固定时间内是否有过触发,如果有,就需要补时间(wait-last)。前缘触发的执行在闭包的apply中,后缘触发的执行在later函数中。

函数分时

分时函数可以将大量的dom操作或者性能耗费大的任务按时间分批执行。

1
2
var x = setInterval(func,millisec);
clearInterval(x);

分时函数需要setInterval和clearInterval来实现,setInterval函数会按周期性执行回调函数。而clearInterval函数则用来取消设定的Interval。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var timeChunk = function(dataArray,func,count,duration) {
var timer;
var isRunning = false;
var exec = function() {
if(count<=0) count = 1;
for (var i = 0; i < Math.min(count,dataArray.length); i++) {
var data = dataArray.shift();
func(data);
}
}
return function() {
if (isRunning) return;
isRunning = true;
timer = setInterval(function() {
if (dataArray.length == 0) {
clearInterval(timer);
isRunning = false;
}
exec();
},duration);
}
}