Web前端之路让前端飞

Promise 完全解读

2017-08-15  本文已影响215人  青栀灬

目录

一. Promise 简介

1. Promise 是什么?

ES6 出现的目标是为了使 JavaScript 语言可以编写大型的复杂应用程序,使之成为企业级的开发语言,Promise 也是其中的一环。

从语法上讲 Promise 是个内置对象,抽象来看,它像是一个容器,里面保存着一个异步操作的执行结果,Promise 提供了一套 API 以保证所有异步操作的统一处理方法。

2. 我们为什么需要 Promise?

我们先来举一个简单的 “栗子”,某人需要做三件事(A,B,C),并且要按照这个顺序依次执行,现在将这个转换为传统的代码方式:

// 首先我们要先定义这三件事(A,B,C)分别为三个函数
// 这三个函数都需要提供一个回调来表示事情的结束
function A(callback) {
  console.log('Do A.');
  callback();
}

function B(callback) {
  console.log('Do B.');
  callback();
}

function C(callback) {
  console.log('Do C.');
  callback();
}

// 现在,如果我想要依次做这三件事我需要这样。。。
A(function() {
  // 其它的一些代码
  B(function() {
    // 其它的一些代码
    C(function() {
      // 其它的一些代码
    });
  });
});

可以看出,当涉及到异步操作时,曾经的大部分方式都是靠回调的嵌套来实现的,然而这样的方式造成了几个十分严重的问题:

综上所述,我们需要一种更好的解决办法在某些(并不是全部)方面来取代回调(callback)方式的异步操作。

3. Promise 能解决什么?

4. Promise 的特点

Promise 是基于状态的,一个 Promise 对象表示了一个异步操作,而这个操作会有三种状态:Pending(进行中)、Resolved(已成功)和 Rejected(已失败)。只有异步操作的执行结果可以决定是哪一种状态,任何其它操作都无法改变。而且,一旦状态改变,就不会再变化。

Promise 的异步操作不需要像回调一样执行异步操作后立即就会调用回调,Promise 允许任何时候都可以得到这个异步操作的结果。也就是说它其实和事件的机制是完全不同的,事件触发时,如果你没有处于监听状态,那么错过了就再也得不到此次事件的结果,而 Promise 则是将结果状态会凝固,等待你去观察这个异步操作的结果。

5. Promise 的缺点

一旦构造了 Promise 实例就代表执行了一个异步操作,也就是说它会立即执行,并且中途无法取消。

如果不设置回调,Promise 内部抛出的错误,不会反应到外部。(有利有弊,具体看怎么用)

Promise 其实只适合单一的异步程序,并不适合不断发生的事件处理,所以使用时,要找好最适合使用的场景。

二. Promise API 解析

1. Promise 构造方法

Promise 本身就是个构造函数,用来生成 Promise 实例。

Promise 构造函数接受一个函数作为参数,而该函数的两个参数分别为 resolve 和 reject,它们分别是两个函数,由 JavaScript 引擎提供。

resolve 函数的作用是将 Promise 对象的状态从 Pending 变为 Resolved,在异步操作成功时调用,并将异步操作的结果作为参数传递出去。

reject 函数的作用是将 Promise 对象的状态从 Pending 变为 Rejected,在异步操作失败时调用,并将异步操作失败所抛出的错误,作为参数传递出去。

const promise = new Promise((resolve, reject) => {
  // 模拟一个异步操作
  setTimeout(function() {
    if ( /* 异步操作成功 */ ) {
      // 异步操作成功,携带载荷将状态变为 resolved
      resolve(payload);
    } else {
      // 异步操作失败,携带错误将状态变为 rejected
      reject(error);
    }
  }, 1000);
});

Promise 实例生成后,异步操作就已经开始执行了。

要注意,resolve 和 reject 的调用是对 Promise 对象状态的变更和数据的传递,并不会影响函数的执行,所以 resolve 和 reject 后面如果有可以正常执行的流程代码,它们仍然会被正常执行。如果不想这样,可以使用 return 强制函数执行的结束。

2. Promise 实例方法

(1) Promise.prototype.then()

Promise 实例生成以后,可以用 then 方法来指定 Resolved 状态和 Rejected 状态的回调函数

promise.then((payload) => {
  // Resolved 时执行
}, (error) => {
  // Rejected 时执行
});

Promise.prototype.then() 的两个参数都需要传递函数,代表着两个状态转变所要执行的回调,每个回调都可以接受 Promise 对象状态转变时传出的值作为参数。其中,then() 的第二个函数是可选的。

回到一开始我们举的 “栗子”,现在,我想通过 Promise 的方式来实现多个异步程序的依次执行,我们可以在 then() 的调用中显式的返回一个新的 Promise 实例,然后就可以链式的且依次的执行 then(),看下面的代码:

const promise = new Promise((resolve, reject) => {
  // 模拟异步程序 1:睡觉 1s
  setTimeout(() => {
    resolve('睡完觉了');
  }, 1000);
});

promise.then((val) => {
  console.log(val);
  return (new Promise((resolve, reject) => {
    // 模拟异步程序 2:吃饭 1s
    setTimeout(() => {
      resolve('吃完饭了');
    }, 1000);
  }));
}).then((val) => {
  console.log(val);
  return (new Promise((resolve, reject) => {
    // 模拟异步程序 3:喝水 1s
    setTimeout(() => {
      resolve('喝完水了');
    }, 1000);
  }));
}).then((val) => {
  console.log(val);
});

通过在 then() 的第一个回调函数中,返回新的 Promise 实例,我们可以用同步的流程将异步的操作表示出,相比使用 callback 更加的逻辑清晰。

(2) Promise.prototype.catch()

Promise.prototype.catch 方法是 .then(null, rejection) 的别名,用于指定发生错误时的回调函数,也可以捕获 then() 运行中所抛出的错误。

一般来说,好的方式是不再 then() 里面指定 Rejected 的回调,而是使用 catch() 来对所有错误的捕获(包括 then() 里面抛出的错误)

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});

promise.then(() => {
  return temp + 2;
}).catch((error) => {
  console.error(error);
});

// ReferenceError: temp is not defined

3. Promise 类级别方法

(1)Promise.all()

Promise.all() 方法用于将多个 Promise 实例包装成一个新的 Promise 实例。

Promise.all() 接受一个类数组(具有 Iterator)作为参数,每个成员应为 Promise 实例,如果不是,会调用 Promise.resolve() 方法将其转换为 Promise 实例。

Promise.all() 返回的新的 Promise 实例的状态由传递的类数组成员的共同状态决定:

如果作为 Promise.all() 参数的 Promise 实例自己定义了 catch 方法,那么它的状态变为 Rejected 时,只会触发它自己的 catch(),不会触发 Promise.all() 的 catch()

(2)Promise.race()

Promise.race() 方法和 Promise.all() 几乎是一样的,只是对状态的处理存在差别。

Promise.race() 返回的新的 Promise 实例的状态由传递的类数组成员中最先改变状态的成员的状态决定。

(3)Promise.resolve()

Promise.resolve() 将现有对象转换为 Promise 对象。

Promise.resolve() 的参数分成四种情况:

(4)Promise.reject()

Promise.reject() 会返回一个新的 Promise 实例,状态为 Rejected。

Promise.reject() 等价于下面的写法:

Promise.reject(obj);
// 等价于
new Promise((resolve, reject) => reject(obj));

Promise.reject() 会将参数原封不动的作为 reject() 的参数。

三. Promise 深度剖析

1. Promise 与 setTimeout

我们来看这样的一段代码:

setTimeout(() => {
  console.log(1);
}, 0);

(new Promise((resolve, reject) => {
  resolve();
})).then(() => {
  console.log(2);
});

console.log(3);

这段代码的运行结果的顺序是 3,2,1,原因如下:

2. 多层级 .then()

一个 Promise 实例可以连续使用 .then() 来绑定回调函数:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});

promise.then(() => {
  console.log(1);
}).then(() => {
  console.log(2);
});

// 1
// 2

如果在 .then() 的回调中显式的指定一个返回值(非 Promise 实例),这个值会被作为下一个链式 .then() 回调函数中的参数:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});

promise.then(() => {
  console.log(1);
  return 'Promise';
}).then((val) => {
  console.log(val);
});

// 1
// Promise

如果在 .then() 的回调中返回的是一个 Promise 实例,那么下一个链式 .then() 会在这个 Promise 实例的状态变更时会被调用,并且 resolve 所传递的值会被作为下一个链式 .then() 回调函数中的参数:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve();
  }, 1000);
});

promise.then(() => {
  console.log(1);
  return (new Promise((resolve, reject) => {
    setTimeout(function() {
      resolve('Promise');
    }, 1000);
  }));
}).then((val) => {
  console.log(val);
});

// 1
// Promise
上一篇 下一篇

猜你喜欢

热点阅读