谢谢n͛i͛g͛h͛t͛i͛r͛e͛
大大指出的关于Promise
中catch
用的不到位的错误,贴上大大推荐的文章Promise中的菜鸟和高阶错误,文章很详细说明了一些Promise
使用中的错误和指导。另外更正内容在后面补充。
从 jQuery $.Deferred() 开始
说到异步流程控制,之前用的比较多的是jQ的Deferred。那Deferred是个啥呢,不清楚没关系,直接控制台来打印看下:
喔!看得出$.Deferred()后是个对象,其下面有着熟悉的done
, fail
, always
字眼(对,,是不是有点熟悉了呢?没错!如果经常用ajax的话就会经常接触到这些货色)。 当然了,不止这些,还有最最最重要的reject
和resolve
方法,说到这两个方法,就得引出下Deferred的状态机制了——其实很简单,实例化后用上图中的state
方法就可以查看($.Deferred().state()
),有三种状态
执行resolve/reject前,返回值是pending
执行了resolve,返回值是resolved
执行了reject,返回值是rejected
直接来试着用下吧!这里我们假设执行一个随机延时的setTimeout
的异步操作,在setTimeout
异步操作结束后,根据延时大小,做出不同回应 ! 代码:
function log (msg) {
console.log(msg);
}
// 包装一个异步操作
var Async = function () {
// 生成一个0到5秒的延迟
var delay = Math.floor(Math.random() * 5);
// 创建一个Deffered对象
var dfd = $.Deferred();
// 这里调用一个异步操作
setTimeout(function(){
if (delay <= 2) {
// 置dfd状态为resolved
dfd.resolve('一切正常!');
} else {
// 置dfd状态为rejected
dfd.reject('超时了!');
}
}, delay * 1000)
// 这里要返回Deferred下的promise对象Dererred对象的原因下面会解释
return dfd.promise();
}
Async()
.done(function (data) {
log(data) // 如果延迟不大于三秒 输出dfd.resolve()中的数据 '一切正常!'
})
.fail(function (err) {
log(err) // 反之则 输出dfd.reject()中的数据 '超时了!'
})
.always(function () {
log('执行完毕!'); // 总是输出 '执行完毕!'
})
尝试下通俗理解整个流程就是
在某个操作开始前创建一个
Deferred
对象,然后执行操作操作间可根据情况给dfd执行
relove
或者reject
方法改变状态并传入数据最后返回出dfd的对象下的一个promise对象,这里不直接返回dfd对象是因为dfd对象的状态是在第一次resolve或者reject后还可以更改的(不过里面的数据以第一次为准)!!
操作执行后用
done
和fail
方法分别接受resolve和reject状态和数据(一一对应)然后执行回调(其实1.8还有个then
方法,接受两个参数,第一个参数为resolve
的回调,第二个为reject
的)
always
是无论resolve
还是reject
都会执行。
讲个比较烂的比喻啊
我是一个流水线车间质检工人,就在平常的这样的一天,来了一批玩具熊,嗯,接下来应该是这样的
来了一个检查目标(
$.Dererred()
),这时你还不知道它是好是坏我靠我几十年的新东方炒菜技巧检验产品并给良品贴上了合格标签(
dfd.res* olve(合格标签)
),次品贴上回厂标签* (dfd.reject(回厂标签及原因)
)然后通过的良品和次品都来到了各自的包装口打好包,不能对里面的标签做更改了!(
dfd.promise()
)去往自己下一个目的地(return dfd.promise
)再然后良品来到了熊孩子手中(
.done()
),次品回到了厂里(.fail()
),最后不管玩具熊到了哪里,其实都会被开膛破肚(.always()
好吧这里有点牵强)
这里再上一张图来解释下!
还有值得说一下的是always
里的回调,我在实际中使用时发现总是在done
和fail
里的回调(假设为同步)执行完毕后后执行的。
金掌银掌仙人掌 掌声有请 ES6 Promise
和上面一样,先打印一下!
可以看到Promise下也有熟悉的resolve
和reject
方法,好像和jQ的Deferred
颇为相似!但是不是少了点什么呢?done
或者fail
之类的流程控制的方法呢??
不急,其实展开prototype
原型上就可以看到挂载着的then
方法了!(像极了jQ1.8后那个then
,不过我觉得应该说是jQ来遵循Promise
才对)
Promise其实就是个构造函数,还是之前的例子,这里我们分三步走
var Async = function () {
// 第一步,新建个promise对象,所需的异步操作在其中进行
var prms = new Promise(function(resolve, reject){
// 生成一个0到5秒的延迟
var delay = Math.floor(Math.random() * 5);
// 这里调用一个异步操作
setTimeout(function(){
// 第二步, 根据情况置promise为resolve或者reject
if (delay <= 2) {
// 置dfd状态为resolved
resolve('一切正常!');
} else {
// 置dfd状态为rejected
reject('超时了!');
}
}, delay * 1000)
})
// 第三步,返回这个Promise对象
return prms
}
// 强大的来了
Async()
// then接受两个函数分别处理resolve和reject两种状态
.then(
function(data) {
console.log(data) // 一切正常!
},
function(err) {
console.log(err) // 超时了!!
})
粗粗一看好像和Dererred
不能更像了,,不过细心点的话可以发现我们在函数里直接返回了prms
这个对象,而不是像之前把包装了一层。。。对!因为Promise
的特性就是一旦第一次赋予了状态后面就无法更改了,这也算省心多了吧。但是问题来了,我为什么要选择用Promise
呢??
这么说吧,它是原生的 它是原生的 它是原生的!,还有可以链式链式链式链式调用!,我们可以把每一个then
或者catch
当做一个处理器, 比如这样
Async()
// 这里暂时只处理resolve
.then(function(data) {
console.log(data) // 一切正常!
return Promise.resolve('随便什么');
})
// 下一个then处理器接收到上一个处理器发出的数据
.then(function(data2) {
console.log(data2) // 随便什么
return Promise.reject('错误数据');
})
...
对!没看错,其实在then
里面你还可以return
其他的promise
对象传并递数据!更有甚你甚至可以什么都不返回,比如说这样
Async()
.then(function(data) {
console.log(data) // 一切正常!
})
// 上面那个处理器如果不return任何东西 就会默认返回个resolve(undefined)
// 然后下面的处理器就会接收到这个resolve(undefined)
.then(function(data2) {
console.log(data2) // undefined
// 虽然没有数据来处理,但是你还可以在这里做一些事情啊,例如
return Promise.reject('错误数据');
})
// 嗒哒,catch就这么登场了,这里用catch处理上个then处理器发出的reject
.catch(fucntion(err){
console.log(err) // 错误数据
return '那直接返回个字符串呢?'
})
// 上个catch处理器返回了个字符串其实也会被下个处理器接受
// 相当于resolve('那直接返回个字符串呢?')
.then(function(data3){
console.log(data3) // 那直接返回个字符串呢?
})
// 好,接着我们来试试在没有返回任何东西的情况下接一个catch处理器
.catch(function(err2){
console.log(err2)
// 我们可以来猜一下上面会输出什么,undefined吗?
// 错,其实这里什么都不会输出,因为这个catch接收的是resolve
// 但它并不会吞没这个resolve而是选择跳过,例如我们这里再返回
return Promise.resolve('这个字符串会被跳过')
})
// 这里紧接着个then处理器,它接受到的数据呢
// 其实并不是上个catch返回的resolve('这个字符串会被跳过')
// 而是catch之前那个then处理器默认返回的resolve(undefined)
.then(function(data4){
console.log(data4) // undefined
})
有点被绕晕了吧
我们用一句话来梳理下:
链式调下会有一串then
和catch
,这些then
和catch
处理器会按照顺序接受上个处理器所产生的返回值,并且根据传入的状态做出不同响应,要么跳过,要么处理(所以上面23行处的catch
处理器被跳过了)
ps: 上面我们用的then
处理器只有一个函数参数,所以只会处理resolve
状态,如果是两个then
就可以处理reject
了。
----更新于5月11日-----
catch
使用的注意
上面一块代码中引出了catch
处理器, 之前以为 cacth()
是 then(null, ...)
的语法糖, 其实这么说不完全正确(功能层面上来说这两个是完全相同的没错——都是处理reject
和异常),但是到了实际使用中Promise中的菜鸟和高阶错误文章中给出了明确的情况证明,这里贴一下:
首先只处理异常情况,下面两个是等价的
somePromise().catch(function (err) {
// 处理异常
});
somePromise().then(null, function (err) {
// 处理异常
});
但是,如果不只是处理异常的下面两种情况下就不一样了
somePromise().then(function () {
return otherPromise();
}).catch(function (err) {
// 处理异常
});
somePromise().then(function () {
return otherPromise();
}, function (err) {
// 处理异常
});
不够清楚吗?那么如果是这样呢?如果第一个回调函数抛出一个错误
会发生什么?
somePromise().then(function () {
throw new Error('这里错了!');
}).catch(function (err) {
console.log(err)
// 这里错了! :)
});
somePromise().then(
function () {
throw new Error('这里错了');
},
function (err) {
console.log(err)
// 未知 :(
// 并没有catch到上面那个Error
});
结论就是,当使用 then(resolveHandler, rejectHandler)
, rejectHandler
不会捕获在 resolveHandler
中抛出的错误!
贴完了,好吧,这有什么用呢?
看似这个注意项并不影响平常使用,原文作者也说道:
因为,笔者的个人习惯是从不使用then方法的第二个参数,转而使用 catch() 方法
那么,问题来了,如何正确的使用catch
呢? 其实我没有很好的想明白,希望指教
,随便抛两个砖
// 1
somePromise()
.then(resolveHandler)
// 这个catch会处理somePromise或者resolveHandler的异常
.catch(rejectHandler)
.then(otherResolveHandler)
// 而这个catch呢只会处理resolveHandler的异常
.catch(otherRejectHandler)
// 2
somePromise()
.then(resolveHandler)
.then(otherResolveHandler)
// 至于这个catch则会处理somePromise、resolveHandler和otherResolveHandler的异常
.catch(rejectHandler)
// 3
somePromise()
.catch(console.log.bind(console))
//等价于
.catch(function(err){
console.log(err)
})
哈哈哈哈哈哈,还是好好再去想想Promise去了,弄明白了再来补充,再次谢谢@n͛i͛g͛h͛t͛i͛r͛e͛大大,荆柯刺秦王
写的很粗糙,有错误的地方希望多多指教!!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。