一、Promise
在promise的学习中
但是,除了基础用法,我们必须了解深层次的东西。
首先先来看第一点,也是面试可能会问到的问题。
Promise为什么能够实现异步?
答:Promise会完成控制权的转换。回调能够完成异步,但是什么时候触发回调由其它代码决定,而promise能够将控制权放在自己身上,然后再说js中异步的实现。
-
回调:使用回调函数来封装程序中的continuation,然后把回调交给第三方(甚至可能是外部代码),然后在合适的时机,这个第三方会调用这个continuation,来实现具体的功能。
-
控制权:我们通常使用回调来完成传统的异步实现,此时可以很明显的看出什么时候调用这个回调取决于第三方,即控制权在第三方,这样会出现很多问题:缺乏顺序性和可信任性。
所以,我们就需要将控制权从第三方反转回来,即不把continuation传给第三方,而是希望第三方给我们提供它的能力,然后由我们自己决定做什么,这种范式就是promise。
function ( x ){
// 耗时操作
return listener
}
var evt = foo( 42 )
evt.on("complete", function( ){
// next
})
evt.on("error", function( ){
// 出错了。。。
})
二、Thenable
在promise的使用中,判断某个值是不是真正的promise是很关键的。
你是否有疑问,我用promise构造函数,直接new一个promise,就算是查它的类型,我直接使用instanceof promise来判断不就好了么?这有什么好讲的?
答案是,没这么简单。
在我们常见的promise使用中,至少有3种方式可以创建promise。
new promise(),这也是其它文章中说的标准promise。
使用非es6原生promise,也就是自己实现的promise时。
从其它浏览器窗口接到的promise,这个promise可能与当前窗口不同,所以那种检查方式无法识别promise实例。
所以说,上面说的那种检验方式,不行。
我们需要自己识别。
方法:then()作为promise的配套组件,我们只需要验证有没有then()就可以了,所以,我们提出了thenable的概念。
thenable:任何含有then()方法的对象或函数。
它的结构是不是很像promise,它们都有一个then()。
所以,我们无法找出promise时,就退而求其次,先找类似promise的,也就是thenable。
比如,我们在生活中养宠物,想养一只柴犬,有纯血统的、含有柴犬和其它品种狗狗的血统的、但血统检测很复杂,只能说它们都是柴犬,都在挑选范围内。
但就像无论是哪种血统,都得有柴犬的基本特征才行,检查这些特征,才能知道狗狗是不是柴犬,确定大的范围后,再想办法去确定血统。
同样的,js中也有检查类型,根据一个值的形态(所具有的属性),然后对它的类型做出假定,这种类型检查,一般用术语“鸭子类型”表示。
鸭子类型:如果看起来像鸭子,叫起来像鸭子,那它就是鸭子。
将这个类型检查应用在检查一个值是否为thenable()上也是可以的。
举个例子。
if(p!==null&&(typeof p==="object"||typeof p==="function")&&(typeof p.then==="function")){
console.log("这是一个thenable")
}else{
console.log("这不是一个thenable")
}
这里我们先判断p是否是函数或对象,并且是否有then()方法,满足这些条件,我们将它认定为thenable。
注意:这样做能成功,但是有很大的缺点,如果把一个then()方法加入了函数或对象的原型链中,那么判断当前这个对象有没有then()就是无效的,即使当前的对象没有then(),还是会被识别为thenable。
所以,在编写“鸭子类型”代码时,别忘了先检查原型链。
通过检查thenable,我们可以解决promise中的一些信任问题,promise也提供了一些方法,让我们将thenable彻底变为promise,这样,它的可信任度就变高了。
三、Promise信任问题
只用会掉编码的信任问题,把一个会掉传入工具(三方)时,可能会出现的问题:
- 调用过早
- 调用过晚(或不被调用)
- 调用次数过少或过多
- 吞掉可能出现的错误和异常
Promsie 的特性就是专门针对这些问题提供一个有效的可复用的答案
1、调用过早
这个问题主要是担心代码是否会引入这样的问题。一个任务有时同步完成有时异步完成,可能会导致竞态条件。Promise 即使是立即完成的promise,也无法被同步观察到
2、调用过晚
和前面一点类似,Promise 创建对象调用resolve(...) 或 reject(...)时,这个promise 的then(...)注册的观察回调就会自动调度,可以确信,这些调度的回调在下个异步事件点上一定会触发
p.then(function(){
p.then(function(){
console.log("C")
})
console.log("A")
})
p.then(function(){
console.log("B")
})
// A B C
C无法抢占B,这是promise的运作方式
3、回调未调用
没有任何东西可以阻止(包括js错误)promise 决议,promise总会调用成功和拒绝回调中的一个
4、调用次数过少或过多
promsie 只能被决议一次,一旦状态发生改变就不会更改,回调次数和调用次数保持一致
5、吞掉错误或异常
promise 有自己的异常处理部分,但是有种情况是检测不到的
var p = new Promise(function(resolve, reject){
resolve(42)
})
p.then(
function fulfilled(msg){
foo.bar() // 未定义方法
console.log(msg) // 永远不会到达这里
},
function rejected(err){
console.log(err)
// 不会到达这里
}
);
你可能会说加个catch函数不就好了吗?是可以,但是catch中的异常处理由谁来捕获呢?
var p = new Promise(function(resolve, reject){
resolve(42)
})
p.then(
function fulfilled(msg){
foo.bar() // 未定义方法
console.log(msg) // 永远不会到达这里
},
function rejected(err){
console.log(err)
// 不会到达这里
}
).catch(err=>{
console.log(err)
typeof a
let a = 1
})
后面继续添加catch 或者 finishily?还是会有同样的问题,同时这个问题并不是promise吞掉了错误异常,而是逻辑问题
Promise.resolve
let p1 = Promise.resolve(42)
let p2 = Promise.resolve(42)
p1 === p2 // false
如果向promise.resolve传递一个真正的Promise,就会返回同一个Promise
var p1 = Promise.resolve(42)
var p2 = Promise.resolve(p1)
p1 === p2 // true
如果是thenable类型的非promise,该怎么执行呢?
符合预期
var p = {
then : function(cb){
cb(42)
}
}
p.then(
function fulfilled(val){
console.log(val) // 42
},
function rejected(err){
// 不会到达这里
}
)
不符合预期
var p = {
then : function(cb, errcb){
cb(42)
errcd('evil laugh')
}
}
p.then(
function fulfilled(val){
console.log(val) // 42
},
function rejected(err){
console.log(err) // 'evil laugh'
}
)
Promise.resolve(...)是可以接收任何 thenable 的值,然后将其解封为他的非 thenable 值,从Promise.resolve(...)得到一个真正的Promise,是一个可信任的值
Promise.resolve(p)
.then(
function fulfilled(val){
console.log(val) // 42
},
function rejected(err){
// 永远不会到到达这里
}
)
网友评论