这个是一个很大的话题。我已经看了好几遍了。总觉得都没有完全理解,
这次再总结一下。
回调可能产生的问题:
• 调用回调过早(在追踪之前);
• 调用回调过晚(或没有调用);
• 调用回调的次数太少或太多(就像你遇到过的问题!);
• 没有把所需的环境 / 参数成功传给你的回调函数;
• 吞掉可能出现的错误或异常;
调用过早:调用过晚 :回调未调用
这个问题主要就是担心代码是否会引入类似 Zalgo 这样的副作用(参见第 2 章)。在这类问 题中,一个任务有时同步完成,有时异步完成,这可能会导致竞态条件。
Promise 就不必担心调用过早的问题,因为即使是立即完成的Promise,也总会被异步调用,插在事件队列的末尾。
Promise 创建对象调用resolve(..) 或 reject(..) 时,这个Promise 的 then(..) 注册的观察回调就会被自动调度。可以确信,这些被调度的回调在下一个异步事 件点上一定会被触发
你对一个 Promise 注册了一个完成回调和一个拒绝回调,那么 Promise 在决议时总是会调用其中的一个;
如果Promise 本身永远不被决议,还可以通过
// 用于超时一个Promise的工具
function timeoutPromise(delay) {
return new Promise( function(resolve,reject){
setTimeout( function(){
reject( "Timeout!" );
}, delay );
} );
}
// 设置foo()超时
Promise.race( [
foo(),
// 试着开始foo()
timeoutPromise( 3000 )
// 给它3秒钟
] )
.then(
function(){
// foo(..)及时完成!
},
调用次数过少或过多
promise本身只能被决议一次,但是如果你回调事件注册了两次,那它被调用 的次数就会和注册次数相同。
未能传递参数 / 环境值
你没有用任何值显式决议,那么这个值就是 undefined,如果使用多个参数调用 resovle(..) 或者 reject(..),第一个参数之 后的所有参数都会被默默忽略。
吞掉错误或异常
如果拒绝一个 Promise 并给出一个理由(也就是 一个出错消息),这个值就会被传给拒绝回调。
如果在Promise 的创建过程中或在查看其决议 结果过程中的任何时间点上出现了一个JavaScript 异常错误,比如一个TypeError 或 ReferenceError,那这个异常就会被捕捉,并且会使这个 Promise 被拒绝。
Promise可信任问题
Promise会包装他接收到参数;
如果向 Promise.resolve(..) 传递一个非 Promise、非 thenable 的立即值,就会得到一个用 这个值填充的 promise。
如果向 Promise.resolve(..) 传递了一个非 Promise 的 thenable 值,前者就会 试图展开这个值,而且展开过程会持续到提取出一个具体的非类 Promise 的最终值。
var p = {
then: function(cb,errcb) {
cb( 42 );
errcb( "evil laugh" );
}
};
p
.then(
function fulfilled(val){
console.log( val ); // 42
},
function rejected(err){
// 啊,不应该运行!
console.log( err );
// evil laugh
}
);
这个 p 是一个 thenable,但是其行为和 promise 并不完全一致,它都是不可信 任的。
尽管如此,我们还是都可以把这些版本的 p 传给 Promise.resolve(..),然后就会得到期望,中的规范化后的安全结果:Promise.resolve(..) 可以接受任何 thenable,将其解封为它的非 thenable 值。从 Promise. resolve(..) 得到的是一个真正的 Promise,是一个可以信任的值。
让我们来简单总结一下使链式流程控制可行的 Promise 固有特性。
• 调用 Promise 的 then(..) 会自动创建一个新的 Promise 从调用返回。
• 在完成或拒绝处理函数内部,如果返回一个值或抛出一个异常,新返回的(可链接的) Promise 就相应地决议。
• 如果完成或拒绝处理函数返回一个 Promise,它将会被展开,这样一来,不管它的决议 值是什么,都会成为当前 then(..) 返回的链接 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
},
function(err){
console.log(err)
} );
var rejectedTh = {
then: function(resolved,rejected) {
rejected( "Oops" ); } };
var rejectedPr = Promise.resolve( rejectedTh );
Promise.resolve(..) 会将传入的真正Promise 直接返回,对传 入的thenable 则会展开。
如果这个thenable 展开得到一个拒绝状态,那么从Promise. resolve(..)
返回的 Promise 实际上就是这同一个拒绝状态。
所以resolve是一个完成的状态,它可能会获得一个成功也可能是失败。
Promise的问题:
会容易吞掉错误
var p = Promise.resolve( 42 );
p.then(
function fulfilled(msg){
// 数字没有string函数,所以会抛出错误
console.log( msg.toLowerCase() );
},
function rejected(err){
// 永远不会到达这里
}
);
如果 msg.toLowerCase() 合法地抛出一个错误(事实确实如此!),为什么我们的错误处理 函数没有得到通知呢?
正如前面解释过的,这是因为那个错误处理函数是为 promise p 准 备的,而这个 promise 已经用值 42 填充了。
promise p 是不可变的,所以唯一可以被通知这 个错误的 promise 是从 p.then(..) 返回的那一个,但我们在此例中没有捕捉。
无法取消的 Promise
一旦创建了一个 Promise 并为其注册了完成和 / 或拒绝处理函数,如果出现某种情况使得 这个任务悬而未决的话,你也没有办法从外部停止它的进程。