JS 异步

什么是异步

1
2
setTimeout(()=>{console.log("路")},1000);
console.log("遥"); // 遥路

在1000s的时间(需要较长时间响应的事件)内执行了下一块代码。
正确说法:任何时候,只要把一段代码包装成一个函数,并指定它在响应某个事件(定时器、鼠标点 击、Ajax 响应等)时执行,你就是在代码中创建了一个将来执行的块,也由此在这个程序 中引入了异步机制。

传统的实现异步的方式

为什么我们会需要使用异步,因为我们不想浏览器发生阻塞,想象我们手机卡住的场景,这简直难以忍受;但是通过回调引发的异步同样会引发新的问题(当然,这些问题与引起的阻塞相比都是小问题)。(其中影响最大的“信任问题”。)

回调引发的问题

顺序问题->追踪难度加大
1
2
3
4
5
6
7
8
doA(function(){
doC();
doD(function(){
doF();
});
doE();
} );
doB();

或许能够追踪到A → B → C → D → E → F这样的执行顺序,可是真正的异步实际情况会复杂很多。而且在某些情况下,doA顺序执行了,而在你的代码中,函数的执行结果很依赖于各个函数的执行顺序,这时候你很难追踪到是那个部分出了问题。当然也可以通过硬编码(调整函数代码块的位置和设置if语句)去排除这些问题,但是这样就将代码复杂化了。所以我们需要更好的异步模式。

信任问题
1
2
3
4
5
//A
ajax("..",function(...){
//C
});
//B

有时候 ajax(..)(也 就是你交付回调 continuation 的第三方)不是你编写的代码,也不在你的直接控制下。多数情况下,它是某个第三方提供的工具。我们把这称为控制反转(inversion of control),也就是把自己程序一部分的执行控制交给某个第三方。(出现的不可信任的第三方)。
由于某些原因,c没有回调成功,执行ajax这个工具进行了会在五秒钟内每秒重试一次传入的回调函数,然后才会因超时而失败。之后这5个回调成功了,c被执行了5次。如果c是一个支付相关的,则用户会被扣款五次。当然这也可以通过设置if条件,防止第三方工具瞎搞。出了重复回调外,还会有其他问题

  • 调用回调过早(在追踪之前)
  • 调用回调过晚(或没有调用)
  • 调用回调的次数太少或太多(就像你遇到过的问题!)
  • 没有把所需的环境 / 参数成功传给你的回调函数
  • 吞掉可能出现的错误或异常

为了解决这一系例隐患,我们不知道要增加多少工作量,所以我们迫切需要新的异步模式!

新的异步模式Promise

在 ES6 中,有一个新的概念建立在事件循环队列之上,叫作任务队列(job queue)。事件循环队列类似于一个游乐园游戏:玩过了一个游戏之后,你需要重新到队尾排队才能 再玩一次。而任务队列类似于玩过了游戏之后,插队接着继续玩。一个 Promise (p1)决议后,这个 Promise 上所有的通过 then(..) 注册的回调(cb1,cb2)都会在下一个异步时机点(p2 cb3)上依次被立即调用。这些回调中的任意一个(cb1)都无法影响或延误对其他回调(cb2)的调用。由Promise创建的then异步是基于任务的,而不像cb是基于事件的。

什么是Promise
  • promise作为一个返回值
1
2
3
4
5
6
7
8
9
10
11
function foo(x){

//构造并返回一个promise
return new Promise(
function(resolve,reject){
//promise的决议回调
}
)
}
//new Promise( function(..){ .. } )模式
//?
  • promise事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//相比回调,实现了控制反转,bar不再通过foo的成功调用
var p = foo(2);
bar(p);
baz(p);
function bar(fooPromise){
fooPromise.then(
function(){
//foo success
},
function(){
//foo err
}
);
}
//baz 同
理解then(…)

具有then()函数的对象,都是promise吗?不是。普通then()与promise产生的then的区别?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var p = {
then: function(cb,errb){
cb(42);
errb("evil laugh");
}
};
p.then(
function fulfilled(val){
console.log(val); //42
},
function rejected(err){
console.log(err); //evil laugh
//如果是真正promise的then方法,这两个是不会都运行的
}
)
Promise如何解决了之前的问题,你看出来了吗

关于信任问题

1
2
3
4
5
6
7
8
9
10
11
12
13
//Promise 构造器
var p = new Promise( function(resolve,reject){
resolve(42);
reject();
})
p.then(
function fulfill(x1){
console.log(x1);//完成后 42
},
function reject(x2){
console.err(x2);
}
)
  • 回调未调用
    reject事件
    但如果Promise永远不被决议呢,Promise提供了解决方案,一种称为竞态的解决方案

  • 调用次数过多或过少
    由于 Promise 只能被决议一次,且决议后的结果不会被更改,所以任何通过 then(..) 注册的(每个)回调就只会被调用一次。如果你把同一个回调注册了不止一次(比如p.then(f); p.then(f);),那它被调用的次数就会和注册次数相同

  • 调用过晚
    在下一个异步点之前一定会结束这个任务。

  • 未能传递参数/环境值
    依靠注册的回调传递

  • 吞掉错误或异常
    reject能捕捉到

1
2
3
4
5
6
7
8
9
10
11
12
var p = new Promise( function(resolve,reject){
foo.bar(); //foo undefined
resolve(42);
} )
p.then(
function fufilled(){
//will not reach here
},
function rejected(err){
// err将会是一个TypeError异常对象来自foo.bar()这一行
}
)
如何将传统的回调异步转换为Promise异步模式
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
//回调
function add(getX,getY,cb){
var x,y;
getX(function(xval){
x = xval;
if(y != undefined){
cb(x+y);
}
})//这种写法?getX里面是一个函数?莫非是高阶函数?xval应该是fetchX的返回结果
getY(function(yVal){
y =yVal;
if(x != undifined){
cb( x+y);
}
})
}
add(fetchX,fetchY,function(sum){
console,log(sum);
})

//Promise模式
function add(xPromise,yPromise) {
return Promise.all([xPromise,yPromise]).then(
function(valus){
//.then会返回一个promise,这个promise由return values[0] + values[1]这一 行立即决议(得到加运算的结果)
return values[0]+values[1];
}
//fulfill(values[0]+values[1]);
)
}
add( fetchX(),fetchY() )
.then(
function(sum){
console.log( sum );
},
function(err){
console.error(err);
}
);
//通过Promise API学习使用

Promise的不足

其一
为了避免丢失被忽略和抛弃的 Promise 错误,一些开发者表示,Promise 链的一个最佳实践 就是最后总以一个 catch(..) 结束,比如

1
2
3
4
5
6
7
8
9
var p = Promise.resolve(42);
p.then(
function fufill(msg){
// 数字没有string函数,所以会抛出错误
console.log( msg.toLowerCase())
}
)
.catch( handleErrors);
//如果 handleErrors(..) 本身内部也有错误怎么办呢?谁来捕捉它?
0%