对比回调函数和Promise

暂时不管Promise是什么,先看一下下面的代码,看一看Promise的好处。需要特别说明的是,在这个对比的中,Promise和回调都没有考虑存在异常的情况。

回调函数执行一次

首先,定义一个回调函数,调用一次,看看这个代码的写法。

'use strict';

// 定义一个计数器,用来统计回调函数执行的次数
let count = 1;

/**
 * 定义一个异步执行函数,参数是回调函数
 */
function asyncFunc(callback) {
  setTimeout(function () {
    callback(`callback ... 执行了 ${count} 次`);
    count++;
  }, 1000);
};

// 调用函数
asyncFunc(function(data){
  console.log(data);
});

/*************************************
callback ... 执行了 1 次
*************************************/

如果用Promise改写,会是什么样的效果呢?

'use strict';

// 定义一个计数器,用来统计回调函数执行的次数
let count = 1;

/**
 * 定义一个异步执行函数,返回值是一个Promise对象
 */
function asyncFunc() {
  let promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve(`resolve ... 执行了 ${count} 次`);
      count++;
    }, 1000);
  });
  return promise;
};

// 调用函数
asyncFunc().then(function(data){
  console.log(data);
});

/*************************************
resolve ... 执行了 1 次
*************************************/

怎么感觉代码更复杂,更加难于理解了?确实,在定义函数的时候,需要返回一个Promise对象,增加了代码量,看着没什么优势。从这点来看,学这个东西,完全没有必要啊。

别着急,接着往下走。。。。

回调函数执行多次

上面的情况只是调用一次函数,那么调用多次呢?比如调用个五次、七次。下面咱们看看调用七次的情况。

首先,还是先看看使用回调函数的情况:

'use strict';

// 定义一个计数器,用来统计回调函数执行的次数
let count = 1;

/**
 * 定义一个异步执行函数,参数是回调函数
 */
function asyncFunc(callback) {
  setTimeout(function () {
    callback(`callback ... 执行了 ${count} 次`);
    count++;
  }, 1000);
};

// 调用函数
asyncFunc(function (data) { // 第一次调用
  console.log(data);
  asyncFunc(function (data) { // 第二次调用
    console.log(data);
    asyncFunc(function (data) { // 第三次调用
      console.log(data);
      asyncFunc(function (data) { // 第四次调用
        console.log(data);
        asyncFunc(function (data) { // 第五次调用
          console.log(data);
          asyncFunc(function (data) { // 第六次调用
            console.log(data);
            asyncFunc(function (data) { // 第七次调用
              console.log(data);
            });
          });
        });
      });
    });
  });
});

/*************************************
callback ... 执行了 1 次
callback ... 执行了 2 次
callback ... 执行了 3 次
callback ... 执行了 4 次
callback ... 执行了 5 次
callback ... 执行了 6 次
callback ... 执行了 7 次
*************************************/

看着挺好看,但是,当回调更多的时候,如何分清是哪个回调,又怎么判断哪个括号对应哪句代码呢?其实这就是一个回调地狱,这样的代码可读性差。

这个时候,Promise就可以发挥作用了。

看了回调函数,再来看看使用Promise的情况:

'use strict';

// 定义一个计数器,用来统计回调函数执行的次数
let count = 1;

/**
 * 定义一个异步执行函数,返回值是一个Promise对象
 */
function asyncFunc() {
  let promise = new Promise(function (resolve, reject) {
    setTimeout(function () {
      resolve(`resolve ... 执行了 ${count} 次`);
      count++;
    }, 1000);
  });
  return promise;
};

// 调用函数
asyncFunc() // 第一次调用
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第二次调用
  })
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第三次调用
  })
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第四次调用
  })
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第五次调用
  })
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第六次调用
  })
  .then(function (data) {
    console.log(data);
    return asyncFunc(); // 第七次调用
  })
  .then(function (data) {
    console.log(data);
  });
  
/*************************************
resolve ... 执行了 1 次
resolve ... 执行了 2 次
resolve ... 执行了 3 次
resolve ... 执行了 4 次
resolve ... 执行了 5 次
resolve ... 执行了 6 次
resolve ... 执行了 7 次
*************************************/

会发现,是用了Promise代码可读性变得很好,以后也便于修改。

用Promise包装旧回调写法

现在可以看出Promise的好处,如果想以后都使用Promise,可否实现?

肯定可以实现啊,下面就使用Promise来包装上面的回调函数。

使用Promise包裹的时候,只需要经过下面几个步骤:

  1. 定义一个函数,返回Promise对象
  2. 在Promise对象中调用异步执行函数,参数是创建Promise对象时传递的函数
'use strict';

// 定义一个计数器,用来统计回调函数执行的次数
let count = 1;

/**
 * 定义一个异步执行函数,参数是回调函数
 */
function asyncFunc(callback) {
  setTimeout(function () {
    callback(`callback ... 执行了 ${count} 次`);
    count++;
  }, 1000);
};

/**
 * 包装异步执行函数,返回一个Promise对象
 */
function wrapperAsyncFunc() {
  let promise = new Promise(function (resolve, reject) {
    asyncFunc(resolve);
  });
  return promise;
};

// 调用
wrapperAsyncFunc()
  .then(function (data) {
    console.log(data);
    return wrapperAsyncFunc();
  })
  .then(function (data) {
    console.log(data);
  });
  
/*************************************
callback ... 执行了 1 次
callback ... 执行了 2 次
*************************************/

认识Promise

在谷歌浏览器中,我们看看Promise都包含什么:

谷歌浏览器中查看Promise

方法概述

可以看到,在Promise的prototype上有三个方法,也就是实例对象上有三个方法:

new Promise(function(resolve, reject) { ... } );
promise.then(onFulfilled[, onRejected]);
promise.catch(onRejected);

Promise对象本身的方法,就是静态方法:

Promise.all(iterable);
Promise.race(iterable);
Promise.reject(reason);
Promise.resolve();

Promise的三种状态

在认识这些方法之前,先认识一下Promise的三种状态:

  • pending: 初始状态,创建Promise成功后的状态
  • fulfilled: 操作执行成功后的状态
  • rejected: 操作执行失败后的状态

Promise的状态转换

Promise的实例方法

首先,先来创建一个Promise对象,可以根据num的值,来调节执行的函数是resolve还是reject:

new Promise(function(resolve, reject) { ... } );
'use strict';

let num = 3;

// 创建一个Promise对象
let promise = new Promise(function (resolve, reject) {
  if (num > 5) {
    resolve('success ...'); // 操作执行成功执行的函数
  } else {
    reject('failure ...'); // 操作执行失败执行的函数
  }
});

首先明确一点,Promise对象创建的时候,立即执行。可是,既然是立即执行,怎么获取对应的状态值呢?下面就要使用then方法了。

promise.then(onFulfilled[, onRejected]);
promise.then(function (data) {
  console.log(data);
}, function (reason) {
  console.log(reason);
});

上面的代码中,在then方法中需要传递两个回调函数,这样看着会有点乱。有没有更优的解决方式?有,这个时候要使用catch方法。

// 把上面的代码进行简化
promise
  .then(function (data) { // 状态变为fulfilled后执行的回调
    console.log(data);
  }).catch(function (reason) { // 状态变为rejected后执行的回调
    console.log(reason);
  });

Promise的静态方法

为了方法认识Promise.all和Promise.race,定义三个Promise对象:

let promise1 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve('promise1 ...');
    console.log('done1 ...');
  }, 2000); // 延迟2秒
});

let promise2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve('promise2 ...');
    console.log('done2 ...');
  }, 4000); // 延迟4秒
});

let promise3 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve('promise3 ...');
    console.log('done3 ...');
  }, 6000); // 延迟6秒
});

Promise.all

Promise.all的作用是在参数中的所有Promise对象完全执行完成的时候,才会执行自身的then或catch方法:

Promise
  .all([promise1, promise2, promise3])
  .then(function (data) {
    console.log(data);
  })
  .catch(function (reason) {
    console.log(reason);
  });
 
/***************************************
done1 ...
done2 ...
done3 ...
[ 'promise1 ...', 'promise2 ...', 'promise3 ...' ]
***************************************/

Promise.all中所有的Promise对象状态都变为fulfiled状态时,才会触发then方法;其中一个变为rejected状态,那么就触发catch方法。

需要注意的是,即使触发了catch方法,其他的Promise对象中的代码还是会正常执行的。因为这是Promise的特性,创建之后,立即执行。

更改一个Promise对象之后,结果就会成下面的状态:

let promise2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    reject('promise2 ...');
    console.log('done2 ...');
  }, 4000);
});

/***************************************
done1 ...
done2 ...
promise2 ...
done3 ...
***************************************/

Promise.race

Promise.race的作用是在参数中的Promise对象中的一个执行完成的时候,就会执行自身的then或catch方法:

Promise
  .race([promise1, promise2, promise3])
  .then(function (data) {
    console.log(data);
  })
  .catch(function (reason) {
    console.log(reason);
  });

/***************************************
done1 ...
promise1 ...
done2 ...
done3 ...
***************************************/

需要注意的是:Promise.all方法是所有的都执行完成才会触发then方法,就是不落下任何一个人;而Promise.race方法是有一个执行完成就会触发then方法,就是看谁跑得快。

后续的两个方法,以后用到的时候再补充,因为这些内容对现在而言已经够用了。


沫俱宏
763 声望33 粉丝

自己的肯定最重要,做任何决定,一定要从内心出发