You Don't Know JS读书笔记之Promise

前言

回调函数的问题在于他剥夺了我们使用return和throw,这类关键字的能力,把程序执行的控制权交给回调函数,从而带来反转控制(inversion of control)。而Promise很好的解决了这一问题。如下面这个例子,并不是直接给foo()传递一个回调函数,而是return一个接收回调函数的事件-promise。
promise承诺我们一定可以在未来的某个时刻得到某个异步事件的结果,fulfillment或者reject,所以我们可以对结果安排一些之后要执行的操作,而不必担心这个异步事件什么时候执行完毕(time-independent)。这里p获得了这个promise执行的结果,我们可以通过then方法来决定reslove之后执行什么,来处理这些future value。
inversion of inversion从而解决反转控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function foo(x) {
// 这里可以进行一些操作,然后构造并返回一个promise对象
//promise构造函数接收一个函数作为参数,该函数的两个参数分别是fulfillment方法和reject方法。
return new Promise( function(resolve,reject){
if(/*异步操作成功*/){
resolve(value);//如果异步操作成功,则fulfillment方法将对象的状态从Pengding->Fulfilled
}else{
reject(error);//如果异步操作失败,则reject方法将对象的状态Pengding->Rejected
}
} );
}
var p = foo( 42 );//p是这个promise执行的结果
p.then(function(value){
//成功
},function(value){
//failure
} );

Promise

什么是Promise?

  • promise是一个对象,代表某个未来才知道结果的事件,通常是一个异步操作。future value/asynchronous task
  • 中立协商,promise event,提供统一的API(then之类的方法),可以进行下一步处理。completion event

    What if instead of handing the continuation of our program to another party, we could expect it to return us a capability to know when its task finishes, and then our code could decide what to do next? This paradigm is called Promises.
    Promises are an easily repeatable mechanism for encapsulating and composing future values.
    flow-control mechanism

Promise对象的两个特点:

  1. 对象的状态不受外界影响:Promise对象代表一种异步操作,有三种状态Pengding(进行中)、Fulfilled(已完成)、Rejected(已失败)。只有异步操作的结果了一决定当前是那一种状态,任何其他操作都无法改变这个状态。而这些依赖时间的状态都被封装在promise内部,所以promise是time-independent的。不在乎结果、时间,都可以安排下一步的操作。
  2. 一旦状态改变,就永久不会再变,任何时候都可以得到这个结果:Promise对象改变只有两种可能,Pengding->Fulfilled、Pengding->Rejected。当promise对象状态发生改变,再对对象添加回调函数,也会立即得到这个结果,这一点与(Event)完全不同!

    事件的特点是:如果你错过它,再去监听,是得不到结果的。

Thenable Duck Typing

Then-able,指的是一个对象/函数,有一个方法名为then,那么这个对象就称之为Thenable,他的behavior就类似promise,这个时候,如果调用对象.then(),这个then方法里面又没有任何回调函数作为参数,那么这个事件就会永远被挂着,而不被处理。这是很恐怖哒,要尽量避免~

Promises如何解决信任问题

过早调用:
before it’s been tracked,主要是解决zalgo问题:一个任务有的时候同步执行,有的时候异步执行,有时会导致一个race conditions,但是当你在Promise对象上调用then的时候,不管promise是否已经resolve,都不会是同步执行的,所以可以避免运行顺序导致的竞态问题。
过迟调用:
当promise resolve的时候,所有通过then注册的callback会自动在下一个异步时刻(Jobs)按照顺序依次调用,callback中发生的事情不会影响其他callback。

1
2
3
4
5
6
7
8
9
10
p.then( function(){
p.then( function(){
console.log( "C" );
} );
console.log( "A" );
} );
p.then( function(){
console.log( "B" );
} );
// A B C-----即.then是一个异步的操作

从不调用:
提供很多的promise超时模板,设定一定等待时长。promise.race()等,重要的是,给出一个这个事件的结果信号,避免这个promise一直被挂着。
调用多次
promise只能被调用一次,状态一经改变就永久保持不变,会忽视多余的调用。
Promise.resolve()确保返回参数是promise对象。

1
2
3
4
5
6
7
8
9
10
11
// don't just do this:
foo( 42 )
.then( function(v){
console.log( v );
} );

// instead, do this:
Promise.resolve( foo( 42 ) )
.then( function(v){
console.log( v );
} );

介绍一个promise调用的怪癖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//不要依赖promise的调用顺序,解包unwrap是一个异步的操作
var p3 = new Promise( function(resolve,reject){
resolve( "B" );//reaolved with a immediate value
//就是一个immediate fulfilled promise(值得是Jobs里面而不是严格意义上的now)
} );
var p1 = new Promise( function(resolve,reject){
resolve( p3 );//将p3赋值给p1,unwrap是一个一部的操作,所以A在前面输出
} );
var p2 = new Promise( function(resolve,reject){
resolve( "A" );
} );
p1.then( function(v){
console.log( v );
} );
p2.then( function(v){
console.log( v );
} );// A B <-- not B A as you might expect

流程控制链

promise链–异步流序列–流控制 (string multiple Promises together to represent a sequence of async steps):

  • 每次调用then都会返回一个新创建的Promise对象;
  • then()这个函数会接受一个回调函数,这个回调函数返回的值将使then()返回的promise resolved(也就是完成了),并且这个值会通过promise链传给下一个promise对象。
  • 使得promise在每一步真正异步的关键是我们传递的是一个promise对象或者是一个thenable,而不是一个可以立即获得的值。解包接收到的对象/thenable的过程是一个异步的过程。如果fulfillment、rejected返回的是一个对象,就解包,值传递。
  • error会在promise链中一直传播,知道遇到一个显示定义的拒绝处理程序-rejected。
  • then()方法,省略fulfillment就会默认接受什么值,传递什么值,省略reject就会默认throw这个错误。
1
2
3
4
5
6
7
8
9
10
var p = Promise.resolve( 42 );
p.then(
// assumed fulfillment handler, if omitted or any other non-function value passed
// function(v) {
// return v;
// }
null,
function rejected(err){
// never gets here
});//then(null,function(error){...})同.catch()方法

术语:resolve、fulfill、reject

  1. resolve表示完成了的意思,可以是fulfill成功也可以是reject;
  2. promise.resolve()确保返回一个promise对象,如果直接是原生的promise对象,直接传递;如果传递的是thenable像是reject状态,也会解包thenable,取得这个reject状态,传递下去。
  3. promise.reject()没有解包操作这样的能力,如果传递的是一个对象,或者thenable不会解包获得里面的值,直接将对象用作失败原因,在链中传递。
  4. new promise构造promise时第一个参数用resolve,then方法里面用fulfilled参数。

错误处理

一般错误处理使用try-catch,但是由于try-catch不支持异步,所以无法捕抓到异步运行函数的错误。
回调函数中,有一个标准叫做 error-first callback, 广泛应用于nodeapi种,例如:

1
2
3
4
5
fs.readFile(path, function (err, content) {
if (err) {
//处理错误
}
})

而在Promise中,采用的是split callbacks ; 一个函数负责处理”fulfillment” , 另外一个处理`”rejection”

1
2
3
4
5
6
7
8
9
10
var p = Promise.reject( "Oops" );

p.then(
function fulfilled(){
// never gets here
},
function rejected(err){
console.log( err ); // "Oops"
}
);

Promise的错误处理还有一些需要注意的地方,看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
var p = Promise.resolve( 42 );

p.then(
function fulfilled(msg){
// numbers don't have string functions,
// so will throw an error
console.log( msg.toLowerCase() );
},
function rejected(err){
// never gets here
}
);

其中这个错误处理函数是永远不会执行的,因为rejected函数是提供给p这个promise的,而promise一旦resolve就是immutable的,所以如果要处理这个错误,就只能留给p.then()执行之后返回的新的promise,因为当前是捕获不到错误的。

如果要处理这种情况,按照Promise的定义:

The then(null,function(err){ .. }) pattern – only handling rejections (if any) but letting fulfillments pass through – has a shortcut in the API: catch(function(err){ .. }).

1
2
3
4
5
6
7
8
9
10
var p = Promise.resolve( 42 );

p.then(
function fulfilled(msg){
// numbers don't have string functions,
// so will throw an error
console.log( msg.toLowerCase() );
}
)
.catch( handleErrors );

到了这里,问题还没有完全的解决,因为handleErrors函数也有可能会出错。继续.catch()多一个错误处理函数也是行不通的。因为另一个错误处理函数也会报错。解决的办法:

1
2
3
4
5
6
7
8
9
10
11
12
13
var p = Promise.resolve( 42 );

p.then(
function fulfilled(msg){
// numbers don't have string functions,
// so will throw an error
console.log( msg.toLowerCase() );
}
)
.done( null, handleErrors );

// if `handleErrors(..)` caused its own exception, it would
// be thrown globally here

如果handleErrors发生异常,那么会直接抛出错误, 从而避免错误被开发者忽略。但是done并不是ES6的标准,如果要使用这种方式,就只能自己封装,或者使用一些比较可靠的Promise库。

Promise模式

Promise.all([ .. ])

  • all()接收一个数组,由promise实例组成,thenable/immediate value也都可以(会被传到resolve()中),当所有的promise都fulfill的时候就按数组顺序以数组形式返回结果,成功并继续异步流,若其中有一个reject,就会丢弃其他成功的promise的返回值,进入错误处理程序。
  • 通常用于在不同接口请求数据然后平拼成自己所需要的数据,通常这些接口之间没有关联(例如:不需要前一个接口的数据作为后一个接口的参数)。
  • 如果接收到一个空数组就会immediate fulfilled。(Jobs中的立刻,不是严格意义上的现在)

Promise.race([ .. ])

  • 同样接收一个数组,不同的是,参数promise数组中的任何一个promise对象,如果变为resolve或者reject的话,该函数就会被返回,并使用这个promise的值进行之后的操作。
  • 如果接收到一个空数组就会永远挂起,never resolve。

Promised的缺陷

  1. 无法取消Promise,一旦新建它就会立即执行,无法中途取消;
  2. 如果不设置回调函数,Promise内部抛出的错误,不会反映到外部;
  3. 当处于pending状态时,无法得知目前进展到哪一阶段(刚刚开始还是即将要完成)。

后记

以上仅仅是我个人看书及查阅资料之后的心得总结及一些摘抄,一定有不足,发现错误的话,还请友人多多指正捏~~
这本书后面还有generate生成器以及一些其他章节,因为某些原因来并没有看,以后看过之后再续写读书笔记~jiangjiangjiang~~