Promise 完全解读

作者: 青栀灬 | 来源:发表于2017-08-15 16:13 被阅读215次

目录

  • 一. Promise 简介
    • Promise 是什么?
    • 我们为什么需要 Promise?
    • Promise 能解决什么?
    • Promise 的特点
    • Promise 的缺点
  • 二. Promise API 解析
    • Promise 的构造方法
    • Promise 的实例方法
    • Promise 类级别方法
  • 三. Promise 深度剖析
    • Promise 与 setTimeout
    • 多层级 .then()

一. 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() {
      // 其它的一些代码
    });
  });
});

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

  • 出现了多层回调,当加上业务代码后,将会使整体显得臃肿且凌乱。
  • 回调的出现导致逻辑的流程不清晰,不具有可读性和维护性。
  • 当嵌套层级过多时会产生大量无用数据的滞留以及数据的混杂。
  • 使用回调函数便完全浪费了 return 和 throw 关键字的能力。

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

3. Promise 能解决什么?

  • 编写大型应用时,一种高级、实用且能解决实际问题的高大上语法。
  • 避免层层嵌套的回调函数,将异步操作以同步操作的流程表达出来,使逻辑更加清晰。
  • 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 实例的状态由传递的类数组成员的共同状态决定:

  • 当所有成员的状态都变为 Resolved,Promise 实例的状态才会变为 Resolved,此时所有成员的返回值组成一个数组,传递给 Promise 实例的回调函数
  • 只要有一个成员的状态变为 Rejected,Promise 实例的状态就变成 Rejected,此时第一个被 Rejected 的实例的返回值会传递给 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() 的参数分成四种情况:

  • Promise 实例:直接返回这个实例。
  • thenable 对象
    • thenable 对象指的是具有 then 方法的对象。
    • Promise.resolve() 会将这个对象转为 Promise 对象,然后就立即执行 thenable 对象的 then 方法,相当于将这个对象的 then 方法作为 Promise 构造函数的参数,返回一个新的 Promise 实例。
  • 其余情况
    • Promise.resolve() 返回一个新的 Promise 对象,状态为 Resolved,使用 then() 时,会将 Promise.resolve() 的参数传递给 then() 的回调函数中。
    • 这个立即 Resolved 的 Promise 对象,实在本次 “事件循环” 的结束时才开始执行。

(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,原因如下:

  • 对于 setTimeout 我们应该都知道,它是放在下一轮 “事件循环” 的开始,所以它一定要在本轮事件结束后才会执行,也就是输出 1 一定要在输出 3 以后
  • 接下来就是 Promise 异步执行的问题,虽然 Promise 实例中并没有真正意义上的异步程序,而是直接将状态变更为 Resolved,且立即使用 then 进行状态的观察,但是实质上,立即 Resolved 的 Promise 是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务,所以,输出 2 一定要在输出 3 以后
  • 最后,就是 Promise 和 setTimeout 之间的问题,上面也说到了,setTimeout 是在下一轮事件的开始,而 Promise 又实在本一轮事件的结束,所以,很明显,输出 1 要在输出 2 以后

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

相关文章

  • Promise 完全解读

    目录 一. Promise 简介Promise 是什么?我们为什么需要 Promise?Promise 能解决什么...

  • ES6-Promise

    参考链接: 1.阮一峰Promise解读 2.Promise用法讲解 Promise含义 Promise是异步编程...

  • 第八周第二天笔记

    ES6之Promise类 1 Promise类基础知识解读 promise类的静态属性方法分类:resolve()...

  • Promise完全详解

    这篇文章是我读《你不知道的js》时做的笔记,如有错误和疑惑请在评论区指出,查看代码高亮优化版原文请点击链接,欢迎w...

  • 完全理解 Promise 实现

    完全理解 Promise 基本实现 网上有很多 Promise 实现方式,看了都不是特别理解。这里以一种更简单的形...

  • async实现解读

    async实现解读 解读前我们要首先关注几个问题: 如何确保async返回一个promise, 而且状态必须等里面...

  • Promise对象

    目前高级浏览器如Chrome、Firefox都已经内置了Promise对象。Promise完全改变了js异步编程的...

  • promise原理解读

    首先我们来看一个例子: 考虑下面一种获取用户id的请求处理 getUserId方法返回一个promise,可以通过...

  • 如何实现精简版的Promise(仅30行代码)

    网络上面已经有很多Promise的教程了。推荐一下这篇文章 知乎: 史上最易读懂的 Promise/A+ 完全实现...

  • 关于promise

    本文章完全参考 阮一峰老师的es6-promise。如果有啥问题可以直接去看原文! 什么是promise,简单地来...

网友评论

    本文标题:Promise 完全解读

    本文链接:https://www.haomeiwen.com/subject/eesrrxtx.html