目录
什么是Promise
Promise
Promise 值
完成事件
Promise “事件”
具有 then 方法的鸭子类型
Promise 信任问题
调用过早
调用过晚
Promise 调度技巧
回调未调用
调用次数过少或过多
未能传递参数 / 环境值
吞掉错误或异常
是可信任的 Promise 吗?
链式流
Promise API
Promise 用法
Promise.all 和 Promise.race 的区别和使用
Promise - finally
Promise 局限性
顺序错误处理
单一值
单决议
惯性
无法取消的 Promise
Promise 性能
什么是Promise
通过如下场景来阐述:
我到快餐店的柜台,点了一个芝士汉堡。通过下订单并付款,我已经发出了一个对某个值(芝士汉堡)的请求。我已经启动了一次交易。但是,通常我不能马上就得到这个汉堡。收银员会交给我某个东西来代替汉堡:一张带有订单号的收据。收据就是一个 IOU
(I owe you
,我欠你的)承诺(promise
),保证了最终我会得到我的汉堡。
所以我得好好保留我的收据,我知道这代表了我未来的汉堡,所以不需要担心, 只是现在我还是很饿!
在等待的过程中,我还可以做点其他的事情,我脑海中已经在想着未来的芝士汉堡了,尽管现在我还没有拿到手。我的大脑之所以可以这么做,是因为它已经把订单号当作芝士汉堡的占位符了。 从本质上讲,这个占位符使得这个值不再依赖时间。这是一个未来值
。
终于,我听到服务员在喊“订单 113”,然后愉快地拿着收据走到柜台,把收据交给收银 员,换来了我的芝士汉堡。换句话说,一旦我需要的值准备好了,我就用我的承诺值(value-promise
)换取这个值本身。
但是,还可能有另一种结果。他们叫到了我的订单号,但当我过去拿芝士汉堡的时候,收银员满是歉意地告诉我:“不好意思,芝士汉堡卖完了。”除了作为顾客对这种情况感到愤怒之外,我们还可以看到未来值
的一个重要特性:它可能成功,也可能失败。
现在值与将来值——-——————
不要小瞧 x + y
。
var x, y = 2;
console.log( x + y ); // NaN
运算 x + y
假定了 x
和 y
都已经设定。也就是说在这里我们假定了 x
和 y
的值都是已决议的,那么试想如果其中一个值未决议(是未来值)就进行计算,那会怎么样?
如果有的语句现在完成,而有的语句将来完成,那就会在程序里引起混乱。例如:如果语句 2
依赖于语句 1
的完成,那么就会有两个输出:要么语句 1
马上完成,一切 顺利执行;要么语句 1
还未完成,语句 2
因此也将会失败。
以x + y
为例,如果它们中的任何一个还没有准备好,那我们就等待两者都准备好。一旦可以就马上执行加运算。下面是通过回调的实现:
function add(getX,getY,cb) {
var x, y;
getX( function(xVal){
x = xVal;
// 两个都准备好了?
if (y != undefined) {
cb( x + y ); // 发送和
}
} );
getY( function(yVal){
y = yVal;
// 两个都准备好了?
if (x != undefined) {
cb( x + y ); // 发送和
}
} );
}
// fetchX() 和fetchY()是同步或者异步函数
add( fetchX, fetchY, function(sum){
console.log( sum ); // 是不是很容易?
} );
上面我们把 x
和 y
都当作未来值来处理。在我们不确定某个值是现在值还是将来值的时候,我们就把它当做将来值来处理,这样可以防止很多意外的发生。说得直白些就是,为了统一处理现在和将来,我们把它们都变成了将来,即所有的操作都成了异步的。
Promise
Promise
值
先来看看,通过 Promise
函数表达 x + y
的例子:
function add(xPromise,yPromise) {
return Promise.all( [xPromise, yPromise] )
.then( function(values){
return values[0] + values[1];
} );
}
// fetchX()和fetchY()返回相应值的promise
add( fetchX(), fetchY() )
.then( function(sum){
console.log( sum );
} );
这样就简单多了。Promise
的决议结果可能是拒绝而不是完成。拒绝值和完成的 Promise
不一样:
完成值总是编程给出的,而拒绝值,通常称为拒绝原因,可能是程序逻辑直接设置的,也可能是从运行异常隐式得出的值
通过 Promise
,调用 then(..)
实际上可以接受两个函数,第一个用于完成情况,第二个用于拒绝情况:
add( fetchX(), fetchY() )
.then(
// 完成处理函数
function(sum) {
console.log( sum );
},
// 拒绝处理函数
function(err) {
console.error( err );
}
);
我们清晰的看见,Promise
采用了分离式回调。Promise
是一种封装和组合未来值的易于复用的机制。
注意: 关于 Promise
需要理解的最强大也最重要的一个概念:一旦 Promise
决议,它就永远保持在这个状态,成为了不变值。(一旦改变不能在改变 承诺者模式)
完成事件
从另外一个角度看待 Promise
的决议:一种在异步任务中作为两个或更多步骤的流程控制机制,时序上的 this-then-that
。
假定要调用一个函数执行某个任务,这个函数可能立即完成任务,也可能需要一段时间才能完成。我们只需要知道它什么时候结束,这样就可以进行下一个任务。即我们想要通过某种方式在程序完成的时候得到通知。
侦听某个通知,我们就会想到事件,我们的脑海中可能会出现如下的伪代码:
foo(x) {
// 开始做点可能耗时的工作
}
foo( 42 )
on (foo "completion") {
// 可以进行下一步了!
}
on (foo "error") {
// 啊,foo(..)中出错了
}
当然这样的代码,Javascript
并不提供,更自然的表达方法是:
function foo(x) {
// 开始做点可能耗时的工作
return listener; // 构造一个listener事件通知处理对象来返回
}
var evt = foo( 42 );
evt.on( "completion", function(){
// 可以进行下一步了!
} );
evt.on( "failure", function(err){
// 啊,foo(..)中出错了
} );
bar( evt ); // 让bar(..)侦听foo(..)的完成
这里没有把回调传给 foo(..)
,而是返回一个名为 evt
的事件注册对象,由它来接受回调。此处的反转显而易见,我们通过反转再反转,拿回了对代码的控制权,即调用代码将控制权反转给第三方,再从第三方那里反转回来。对控制反转的恢复实现了更好的关注点分离,即bar
不需要关注foo(...)
的调用细节,foo(..)
也不需要关注bar
是否存在。从本质上说,evt
对象就是分离的关注点之间一个中立的第三方协商机制。
Promise
“事件”
其实上面的事件侦听对象 evt
就是 Promise
的一个模拟。foo(..)
与 bar(..)
的内部实现或许如下
function foo(x) {
// 可是做一些可能耗时的工作
// 构造并返回一个promise
return new Promise( function(resolve,reject){
// 最终调用resolve(..)或者reject(..)
// 这是这个promise的决议回调
} );
}
function bar(fooPromise) {
// 侦听foo(..)完成
fooPromise.then(
function(){
// foo(..)已经完毕,所以执行bar(..)的任务
},
function(){
// 啊,foo(..)中出错了!
}
);
}
注意: 传入Promise
的函数会立即执行,不会像 then(..)
中的回调一样异步延迟
另一种实现方式:
function bar() {
// foo(..)肯定已经完成,所以执行bar(..)的任务
}
function oopsBar() {
// 啊,foo(..)中出错了,所以bar(..)没有运行
}
// 对于baz()和oopsBaz()也是一样
var p = foo( 42 );
p.then( bar, oopsBar );
p.then( baz, oopsBaz );
注意: p.then( .. ).then( .. )
与 p.then(..); p.then(..);
是两个意义,前者p
决议后调用then
,因为.then
返回的总是Promise
,所以才能支持链式调用,此时第二个.then
用的是第一个.then
返回的Promise
的决议值,而后者用的都是p
的决议值。
具有 then
方法的鸭子类型
判断类似于 Promise
的值是否是真正的 Promise
很重要,多见于 Promise.resolve()
对于 Promise
和 thenable
的展开。 thenable
类似于 Promise
,指任何具有 then(..)
方法的对象和函数。
注意: 对象的原型链上若具有 then(..)
方法,那么这个对象也会被识别为 thenable
。
这里主要讲对thenable
的类型检查。
根据一个值的形态(具有哪些属性)对这个值的类型做出一些假定。这种类型检查一般用术语鸭子类型来表示——“如果它看起来像只鸭子,叫起来像只鸭子,那它一定就是只鸭子”。
对 thenable
值的鸭子类型检测就大致如下:
if (
p !== null &&
(
typeof p === "object" ||
typeof p === "function"
) &&
typeof p.then === "function"
) {
// 假定这是一个thenable!
}
else {
// 不是thenable
}
注意
如果有其它代码无意或恶意地给Object.prototype、Array.prototype或者其它原生原型添加了then方法,也会造成灾难。不过鸭子类型有时候还是有用的,只是要小心鸭子类型把不是Promise的值误判为Promise的情况。
Promise
信任问题
Promise
的特性就是专门用来为回调编码的信任问题提供一个有效的可复用的答案。
调用过早
即使是立即完成的 Promise
也无法被同步观察到。即对一个 Promise
调用 then(..)
的时候,即使这个 Promise
已经决议,提供给then(..)
的回调也总会被异步调用(微队列)。所以 Promise
不存在调用过早这个问题。(.then异步微任务)
调用过晚
Promise
创建对象调用 resolve(..)
或 reject(..)
时,这个 Promise
的 then(..)
注册的观察回调就会被自动调度。可以确信,这些被调度的回调在下一个异步事 件点上一定会被触发。所以也不存在调用过晚的问题。
注意: 当 Promise
决议后,其上所有的通过 then(..)
注册的回调都会在下一个异步时机点上依次被立即调用。这些回调中的任意一个都无法影响或延误对其他回调的调用。
举个
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)