Promise 实战
本文适合对Promise已有一定了解的同学,如果你还不知道Promise是什么,可以移步这里Promise API的使用方法,掌握基础的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 化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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化,可以
大大减少不必要的代码。
下面的代码,将会体现这种优势:
1 2 3 4 5 6 7 8 9 10 11 12
| 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
来改善你的代码。
以下是一个图片并行加载的例子,当所有图片加载完成后,再将所有图片一起展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| 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示例中都可以看到类似如下的链式调用的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 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 ,那么执行的代码将会变成这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| 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 )中传入下个方法。
1 2 3 4
| function getTaskInfo(groupInfo,userInfo){ console.log(groupInfo); console.log(userInfo); }
|
4.中断或取消 Promise 链
Promise 标准的API 中并没有提供相应的方法来 中断或者取消 Promise 链的执行,一些库中提供了类似Promise.break
或者 Promise.fail
的方法来中断或取消 Promise 链。利用Promise.catch
的特性来中断 promise链。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class BreakSignal { } Promise .then(() => { }) .then(() => { if (wantToBreakHere) { throw new BreakSignal(); } }) .then(() => { }) .catch(BreakSignal, () => { });
|
只要在 Promise 执行过程中抛出异常,都会直接跳转到 catch 中。但是这样的做法有一个缺点,无法区分程序本身的异常,还是手动抛出的异常。所以需要手动设置一个标识标量,来区分是为了中断执行还是本身的程序异常。
小结
合理的利用 Promise ,可以让代码脉络更加的清晰易懂,流程控制,异常捕获也更加准确。当然为了使用 Promise 也要编写很多额外代码,
想要真正的解决回调问题还得期待ES7的 async
await
关键字的到来,不过在此之前,Promise 都将是解决程序流程控制的最优选择之一。
参考文章
- Promise A+
- Promise迷你书
- Promise的前世今生
- jQuery的deferred对象详解