Promise 实战

发布于 2025-01-25 10:34:14 字数 7227 浏览 14 评论 0

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

需要注意的是, 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
*/

如上面代码所示,我们可以很清楚的理解到程序执行的顺序是

promise

  1. 得到 userInfo
  2. 得到 groupInfo
  3. 得到 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

当要在使用链式 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 都将是解决程序流程控制的最优选择之一。

参考文章

  1. Promise A+
  2. Promise 迷你书
  3. Promise 的前世今生
  4. jQuery 的 deferred 对象详解

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

画尸师

暂无简介

文章
评论
27 人气
更多

推荐作者

梦途

文章 0 评论 0

蓝眼睛不忧郁

文章 0 评论 0

134fengkuang

文章 0 评论 0

yang18

文章 0 评论 0

属性

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文