3.4 链式流
可把多个Promise连接到一起以表示一系列异步步骤。
这种方式可实现的关键在于以下两个Promise固有行为特性:
每次你对Promise调用then(),它都会创建并返回一个新的Promise,我们可将其链接起来
不管从then()调用的完成回调返回的值是什么,它都会被自动设置为被链接Promise的完成。
var p = Promise.resolve(
return v * 2;
} );
// 连接p2
p2.then( function(v){
console.log( v ); // 42
} )
但是,如果必须创建一个临时变量p2,还是有一点麻烦的。谢天谢地,我们可容易地把这些链接到一起
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); // 21
// 用值42完成连接的promise
return v * 2;
})
// 这里是链接的promise
.then( function(v){
console.log( v ); // 42
} )
但这里还漏掉了一些东西。如果需要步骤2等待步骤1异步来完成一些事情怎么办?我们使用了立即返回return 语句,这会立即完成链接的promise。
使Promise序列真正能够在每一步有异步能力的关键是,回忆一下当传递给Promise.resolve()的是一个Promise或thenable而不是最终值时的运作方式。Promise.resolve()会直接返回接收到的真正Promise,或展开接收到的thenable值,并在持续展开thenable的同时递归地前进。
从完成(或拒绝)处理函数返回thenable或者Promise的时候也会发生同样的展开。
var p = Promise.resolve(21);
p.then(function(v){
console.log(v); // 21
// 创建一个promise并将其返回
return new Promise( function(resolve, reject){
// 用值42填充
resolve(v * 2);
})
})
.then( function(v){
console.log( v ); //42
})
虽然我们把42封装到了返回的Promise中,但它仍然会被展开并最终成为链接的Promise的决议,因此第二个then()得到的仍然是42。如果我们向封装的promise引入异步,一切都仍然会同样工作:
var p = Promise.resolve( 21 );
p.then( function(v){
console.log( v ); //21
// 创建一个promise并返回
return new Promise(function(resolve, reject){
// 引入异步
setTimeout( function(){
// 用值42填充
resolve( v * 2 );
}, 100)
})
})
.then(function(v){
// 在前一步中的100ms延迟之后运行
console.log(v); //42
}
现在我们可构建这样一个序列:不管我们想要多少个异步步骤,每一步都能根据需要等待下一步。
当然,在这些例子中,一步步传递的值是可选的。如果不显式返回一个值,就会隐式返回undefined,并且这此promise仍然会以同样的方式链接在一起。这样,每个Promise的决议就成了继续下一个步骤的信号。
我们可把延迟Promise创建过程一般化到一个工具中,以便在多个步骤中复用:
function delay(time){
return new Promise( function (resolve, reject) {
setTimeout( resolve, time );
})
}
delay(100) // 步骤1
.then( function STEP2(){
console.log( "step 2 (after 100ms)" );
return delay( 200 );
} )
.then( function STEP3(){
console.log( "step 3 (after another 200ms)" );
} )
.then( function STEP4(){
console.log( "step 4 (next Job)" );
return delay( 50 );
} )
.then( function STEP5(){
console.log( "step 5 (after another 50ms)" );
} )
没有消息传递的延迟序列对于Promise流程控制来说并不是一个很有用的示例。
function request(url){
return new Promise( function(resolve, reject){
ajax(url, resolve);
} )
}
首先定义一个工具request(),用来构造一个表示ajax()调用完成的promise:
request( "http://some.url.1/" )
.then( function(response1){
return request( "http://some.url.2/?v=" + response1 );
})
.then( function(response2){
console.log( response2 );
} )
我们构建的这个Promise链不仅是一个表达多步异步序列的流程控制,还是一个从一个步骤到下一个步骤传递消息的消息通道。
如果这个Promise链中的某个步骤出错了怎么办?错误和异常是基于每个Promise的,这意味着可能在链的任意位置捕捉到这样的错误,而这个捕捉动作在某种程度上就相当于在这一位置将整条链“重置”回了正常运作:
// 步骤1
request( "http://some.url.1/" )
// 步骤2
.then( function(response1){
foo.bar(); // undefined, 出错
// 永远不会到达这里
return request( "http://some.url.2/?v=" + response1 );
} )
// 步骤3
.then(
function fulfilled(response2){
// 永远不会到达这里
},
// 捕捉错误的拒绝处理函数
function rejected(err){
console.log( err );
// 来自 foo.bar() 的错误TypeError
return 42;
}
)
// 步骤4
.then( function(msg){
console.log( msg ); // 42
})
第2步出错后,第3步的拒绝处理函数会捕捉到这个错误。拒绝处理函数的返回值(42),如果有的话,会用来完成交给下一个步骤(第4步)的promise,这样,这个链现在就回到了完成状态。
如果你调用promise 的then(),并且只传入一个完成处理函数,一个默认拒绝处理函数就会顶替上来:
var p = new Promise( function(resolve, reject){
reject( "Oops" );
});
var p2 = p.then(
function fulfilled(){
// 永远不会到这里
}
// 假定的拒绝处理函数,如果省略或者传入任何非函数值
// function(err){
// throw err;
//}
)
默认拒绝处理函数只是把错误程序抛出,这最终会值得p2用同样的错误理由拒绝。从本质上说,这使得错误 可继续沿着Promise链传播下去,直到遇到显式定义的拒绝处理函数
如果没有给then()传递一个适当有效的函数做为完成处理函数参数,还是会有作为替代的一个默认处理函数:
var p = Promise.resolve(42);
p.then(
// 假设的完成处理函数,如果省略或传入任何非函数值
// function(v) {
// return v;
//}
null,
function rejected(err){
// 永远不会到这里
}
)
默认的完成处理函数只是把接收到的任何传入值传递给下一个步骤而已。
then(null, function(err){}) 这个模式 - 只处理拒绝,但又把完成值传递下去 - 有一个缩写形式API: catch(function(err){})
总结一下使用链式流程控制可行的Promise固有特性
调用Promise的then()会自动创建一个新的Promise从调用返回。
在完成或拒绝处理函数内部,如果返回一个值或抛出一个异常,新返回的Promise就相应地决议。
如果完成或拒绝处理函数返回一个Promise,它将会被展开,这样一来,不管它的决议值是什么,都会成为当前then()返回的链接Promise的决议值。
reject()不会像resolve()一样进行展开,如果向reject()传入一个Promise/thenable值,它会把这个原封不动地设置为拒绝理由。
在来看提供给then()的回调。建议命名为 fulfilled() 和 rejected()
网友评论