Promise 实战
Promise 是什么
Promise 是抽象异步处理对象以及对其进行各种操作的组件。 通俗点讲,Promise 能解决由于回调嵌套带来的流程控制与可读性问题。 promise 已经是成为我们解决回调炼狱的常用方案,而且已经得到官方标准支持,如果你刚刚开始使用 Promise,本文将帮助你了解几个常见的 Promise 的使用场景。
Promise 的历史
早在 1976 年就有人提出 Promise 的概念 。之后的计算机语言发展中,很多语言都提供了与 Promise 相关的特性。而对于 Javascript 语言来说,最早让大家广泛接触的 Promise 相关的库是由 jQuery.Deferred()
对象实现的。随着 Promise/A+ 标准规定了一系列 API,实现该标准的库如雨后春笋版涌现了出来,在最新的 ECMAScript 2015 中已经提供了 Promise 的内置对象,成为了基础库。
一些 Promise 的使用场景
1.原生 API 函数的 Promise 化
大部分原生的 API 函数并不支持 Promise,还是基于回调来使用的,所以需要把一些方法改为返回一个 Promise 对象,这个过程被称为函数的 Promise 化。 下面一个例子将对定时器 setTimeout
Promise 化。
function timer(fn,time){
return function(){
return new Promise( (resolve,reject)=>{
setTimeout(function(){
fn();
resolve();
},time);
});
}
}
Promise.resolve()
.then(
timer(function () {
console.log('1')
}, 1000)
)
.then(() => {
console.log('2');
});
/*
输出结果
1
2
*/
Promise 化本质上都属于一种 Curry 化。Curry 化是指,将需要传递多参数的函数生成一个新的函数,如上代码先通过执行 timer 得到一个新的函数,该函数会返回一个 Promise,这样就完成了 Promise 化。将一些基础的函数进行 Promise 化,可以 大大减少不必要的代码。 下面的代码,将会体现这种优势:
var promise_timer = timer(function () {
console.log('1')
}, 1000)
function promise_timer2(){
return new Promise( (resolve,reject)=>{
setTimeout(function(){
console.log('1');
resolve();
},1000);
});
}
变量 promise_timer
赋予的函数,与函数 promise_timer2
是等价的。 可以看出 setTimeout
Promise 化之后,代码程序可读性更强,代码量也变少了。
2.Promise.all 解决并行任务
当某个函数需要在 N 个回调都完成时才执行,这个时候就可以使用 Promise.all
来改善你的代码。
以下是一个图片并行加载的例子,当所有图片加载完成后,再将所有图片一起展示。
function loadImg(src){
return new Promise( (resolve,reject)=> {
var img = document.createElement("img");
img.src = src;
img.onload = function(){
resolve(img);
}
img.onerror = function(err){
reject(err);
}
});
}
function showImgs(imgs){
imgs.forEach(function(img){
document.body.appendChild(img);
});
}
Promise.all([
loadImg('1.png'), //加载图片
loadImg('2.png'),
loadImg('3.png'),
...
]).then(showImgs); //显示图片
需要注意的是, Promise.all
中传入的 Promise 数组,各自 resolve 之后得到的值,将合并成一个数组传入到 then 中的方法,且数组中 resolve 值的顺序,与 Promise 数组的顺序一致。
3.Promise.then 的链式调用
在许多 Promise 示例中都可以看到类似如下的链式调用的代码。
function getUserInfo(){
console.log('getUserInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
var userInfo = {
name : 'adamchuan'
};
resolve(userinfo);
console.log('getUserInfo end');
},1000);
});
}
function getGroupInfo(userinfo){
console.log('getGroupInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
var groupInfo = {
name : 'jdc'
}
console.log('getGroupInfo end');
resolve(groupInfo,userinfo);
},1000);
});
}
function getTaskInfo(groupInfo,userinfo){
console.log('getTaskInfo start');
return new Promise((resolve,reject)=>{
setTimeout(()=>{
var taskInfo = {
name : 'rebuild'
};
console.log('getTaskInfo end');
resolve();
},1000);
});
}
var p = Promise.resolve();
p.then(getUserInfo)
.then(getGroupInfo)
.then(getTaskInfo);
/* 输出结果
getUserInfo start
getUserInfo end
getGroupInfo start
getGroupInfo end
getTaskInfo start
getTaskInfo end
*/
如上面代码所示,我们可以很清楚的理解到程序执行的顺序是
- 得到 userInfo
- 得到 groupInfo
- 得到 taskInfo
但是如果我们对代码进行一点小的改造,将 then 中的方法不再返回 Promise ,那么执行的代码将会变成这样:
var p = Promise.resolve();
p.then(getUserInfo)
.then(getGroupInfo)
.then(getTaskInfo)
...
function getUserInfo(){
console.log('1');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('2');
resolve();
},1000);
});
}
function getGroupInfo(){
console.log('3');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('4');
resolve();
},1000);
});
}
function getTaskInfo(){
console.log('5');
new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log('6');
resolve();
},1000);
});
}
/* 输出结果
getUserInfo start
getGroupInfo start
getTaskInfo start
getUserInfo end
getGroupInfo end
getTaskInfo end
*/
这是为什么呢?
因为每次调用 then 都会返回一个新的 Promise ,如果 then 中的申明的方法没有返回一个 Promise ,那么会默认返回一个新的 处于 fulfilled 的 Promise ,之后添加的 then 中的方法都会立即执行,所以执行的顺序就变成这样了:
当要在使用链式 Promise 时,请务必在 then 传入的方法中返回一个新的 Promise。
另外一个需要注意的是,resolve 传递给下个 then 方法的值只能有一个,上面 getTaskInfo 方法中是无法获取到 userInfo 的值,所以如果有多个值需要放在一个数据集合( Array , Object , Map , Set )中传入下个方法。
function getTaskInfo(groupInfo,userInfo){ /* userInfo 为 undefined */
console.log(groupInfo); // { name : 'jdc'}
console.log(userInfo); // undefined
}
4.中断或取消 Promise 链
Promise 标准的 API 中并没有提供相应的方法来 中断或者取消 Promise 链的执行,一些库中提供了类似 Promise.break
或者 Promise.fail
的方法来中断或取消 Promise 链。利用 Promise.catch
的特性来中断 promise 链。
/** 用于中断的信号 */
class BreakSignal { }
Promise
.then(() => {
// 开始.
})
.then(() => {
if (wantToBreakHere) {
// 抛出中断信号.
throw new BreakSignal();
}
})
.then(() => {
// 需要跳过的部分.
})
// 接住中断信号.
.catch(BreakSignal, () => { });
只要在 Promise 执行过程中抛出异常,都会直接跳转到 catch 中。但是这样的做法有一个缺点,无法区分程序本身的异常,还是手动抛出的异常。所以需要手动设置一个标识标量,来区分是为了中断执行还是本身的程序异常。
小结
合理的利用 Promise ,可以让代码脉络更加的清晰易懂,流程控制,异常捕获也更加准确。当然为了使用 Promise 也要编写很多额外代码, 想要真正的解决回调问题还得期待 ES7 的 async
await
关键字的到来,不过在此之前,Promise 都将是解决程序流程控制的最优选择之一。
参考文章
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

下一篇: Web Workers 实践实践
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论