promise 由浅入深
首先说一下 “由浅入深” 这个词用的过了,一般说到这个词就已经是可以出书的概念了。 这里的 “由浅入深” 只是一个意愿,也是从出了这个特性就一直用到现在的一些个人小记录。会用的挑着看,不会用的从头看。
背景由来
要说到 JS 和其它语言相比,直观上最“大”(*注1)的特征,也就算是异步回调了。 但是这种回调在编程写法上很不友好,一两个回调还好,一但多起来、尤其是相互套用,这就很“难看”了,也出现了所谓的 “callback hell” 回调地狱。 这种情况对一些逻辑实现和模块设计上都带来一些不便。
这个时候,就出现了一些解决此类问题的方法,到现在,成为公认解决方案的就是 promise 规范。
基础解释
promise 是用一种链式调用的方式,从形式上解决了回调嵌套调用的问题。当然,这只是形式上,因为是 JS 运行机制的问题,无法从根本上解决。
看下面的例子:
/**
* 根据用户ID获取用户信息
* @param uid 用户id
* @callback 回调方法
*/
function (uid, callback) {
$.ajax({
url: '/user/info',
data: {uid: uid},
dataType: 'json',
success: function (data) {
callback({
data: data,
status: 'success',
message: ''
});
},
error: function (err) {
callback({
data: null,
status: 'success',
message: err.state
});
}
});
}
这是一个很典型的异步请求回调的方法,因为 request 请求是异步的,方法执行完不能像同步执行那样直接 return
一个数据,所以一般只能是再传入一个回调方法作为参数来接收处理数据。
这样在方法、模块拆分上就很不直观了。这个时候,promise
就出现了,它可以从代码字面上去掉回调方法的参数,返回『结果数据』。
/**
* 根据用户ID获取用户信息
* @param uid 用户id
* @return 用户数据
*/
function getUserinfo(uid) {
return new Promise((resolve, reject) => {
$.ajax({
url: '/user/info',
data: {uid: uid},
dataType: 'json',
success: function (data) {
resolve({
data: data,
status: 'success',
message: ''
});
},
error: function (err) {
reject({
data: null,
status: 'success',
message: err.state
});
}
});
});
}
这个终于有 return
了,从代码易读性和维护性上已经有很大程序的提高,虽然它并没有实际解决回调问题,看用法:。
var userinfo = getUserinfo('U123321');
userinfo.then((data) => {
/*
{
data: data,
status: 'success',
message: ''
}
*/
}).catch((err) => {
/*
{
data: null,
status: 'success',
message: err.state
}
*/
});
promise 深入用法
Promise.all
有时我们会遇到这种需求:在多个异步请求成功之后,再执行某个操作。比如批量单个上传几张图片后,给予用户一个成功的反馈。
这种需求通过传统的异步回调写地尤为麻烦,在某些情况下甚至是个不能完成的逻辑。
promise
自然也想到了这一点,对多个异步回调后的执行操作专门定了一个 all
方法。
Promise.all
是个静态方法,可以直接调用,唯一参数是一个数组。数组元素各是一个标准的 Promise
实例。如下
const p1 = new Promise(...);
const p2 = new Promise(...);
const p3 = new Promise(...);
const p4 = new Promise(...);
Promise.all([p1, p2, p3, p4])
.then((result) => {
// 所有异步过程都执行成功 (resolve)
})
.catch((err) => {
// 某一异步过程执行失败 (reject)
});
要注意的是,Promise.all
的 then
和 catch
的判定情况:
then
: 所有前置异步过程都执行成功,then 返回的数据就是所有异步过程返回的数据数组catch
: 一个前置异步过程失败就是此失败时的 reject 信息
Promise.race
和以上情况一样,也会遇到这类需求:某几个异步过程,其中有一个完成后,就进行下一步动作。就跟逻辑的 and
和 or
一样,Promise.all
就是 and,Promise.race
就是 or 的特征,也就算是异步回调了。
const p1 = new Promise(...);
const p2 = new Promise(...);
const p3 = new Promise(...);
const p4 = new Promise(...);
Promise.race([p1, p2, p3, p4])
.then((result) => {
// 第一个异步过程成功后的 resolve
})
.catch((err) => {
// 第一个异步过程成功后的 reject
});
race
从字面上已经很形象了 — 赛跑
。就是谁是第一个,不管成功还是失败,就按它说的算。
Promise
是一个很好的异步解决方案,在平常的接口开发中可以适当去用,让自己的逻辑更清晰。
注1:这种特性现在已经不存在了,基本上现有成熟的语言都有了自己的异步执行方式和“事件”回调
下一篇简要说一下 async/await
的异步解决方案。