为什么要promise
期约是用来处理异步操作的,它允许我们编写看似同步的异步代码,并以更清晰、更有条理的方式处理异步操作的结果。
什么是异步操作?
异步操作是指在不阻塞主线程的情况下,在后台进行的操作。也就是说,主线程会继续执行后面的代码,而不等待异步操作的完成。异步操作完成后,通常会通过回调函数或 Promise 对象来通知主线程。
异步操作常见的例子包括:Ajax 请求、setTimeout、setInterval 以及事件处理程序。
使用异步操作的目的是为了避免阻塞主线程,以提高程序的性能。因为如果主线程阻塞了,整个程序都会变得不响应,用户体验会大大降低。
因为js中有异步操作的存在,如果要对异步操作返回的数据做操作,写成普通的同步代码是不行的,因为在异步操作返回数据之前,处理数据代码已经执行了:
let a = getData(); // 假定getData是异步操作,a不会立刻得到
let b = a+1; // 在a得到返回的数据之前,这行代码已经执行了。此时b就为NaN,有什么办法呢?
通常,异步操作可以通过回调函数来实现,这里就不再多说,实在是太麻烦,而且缺点也很大。promise的出现,使得异步编程更加简单,为异步编程打开了一个新的大门,下面我们来学习一下promise.
1. Promise是一个对象
js中,万物皆对象,promise也不例外 咱们先来new一个对象来看看长啥样:
正如你所看到的,期约有两个显著的属性:
PromisState,即期约状态PromiseResult,即期约返回的结果(解决值,或 拒绝的理由)- some methods, 一些方法,用来控制期约状态和结果等等。
理解定义: 期约是对尚不存在结果的一个替身。讲通俗一点就是:期约是预先对某件事情或结果进行预期,但它并不存在。期约是一种代替,它对真实结果进行了预测或预先的安排,但是直到该结果真正存在,这个预期才可能得到实现。
注解:在期约中,结果分为两种:解决值(成功的结果)和拒绝理由(失败的结果)
注意,抓住这两个字: '结果' 和 '实现'。你细品。咱们的目的不就是为了在异步操作(例如请求数据),得到某种'结果' 之后,然后根据结果的不同,实现一些操作嘛。
接着,我们再来看看,如何设置结果,然后根据结果,实现预期。(预期分为两种:成功的预期,失败的预期)
2.期约的状态
期约的结果分为两种,一种成功,一种失败, 结果都可以为任何值,为了区分是哪一种结果,我们给期约约定一个状态,状态为成功那 就是成功的结果,状态为失败,那就是失败的结果。
期约是一个有状态的对象。它处于以下三种状态之一:
- 待定(pending)
- 解决(resolved or fulfilled)
- 拒绝(rejected)
待定是期约的初始状态。这时候,期约还没有结果。
3.控制期约状态
执行器executor
由于期约的状态是私有的,所以只能在内部进行操作。内部操作在期约的执行器函数中完成。
执行器函数(executor)有两大职责:
- 初始化期约的异步行为
- 控制状态的最终转换
控制期约的状态主要是由executor的两个函数参数来实现的。
- 执行resolve()函数,切换状态为
fulfilled即我们所谓的成功(or解决)状态。
let p1 = new Promise((resolve,reject) => resolve());
consloe.log(p1); //[[Prototype]]: Promise
// [[PromiseState]]: "fulfilled"
// [[PromiseResult]]: undefined
- 执行reject函数,切换状态为为
rejected
let p1 = new Promise((resolve,reject) => reject());
consloe.log(p1); //[[Prototype]]: Promise
// [[PromiseState]]: "rejected"
// [[PromiseResult]]: undefined
执行器函数是同步执行的,这是因为执行器是期约的初始化程序。通过下面例子可以说明
new Promise(() => setTimeout(console.log,0,'executor'));
setTimeout(console.log,0,'promise 初始化完成');
// executor
// promise 初始化完成
Promise.resolve()
期约并非一开始就处于待定状态,我们可以使用Promise的静态函数来直接初始化一个设定好状态的期约。
// 其实下面两个期约,实际上是一样的
let p1 = new Promise((resolve,reject) => resolve());
let p2 = Promise.resolve();
resolve()函数接收一个参数,作为期约的结果。即PromiseResult的值。只接收第一个参数,多余的无效。
接下来将一个重点,以后会用到:
对这个静态方法而言,如果传入的参数本身是一个期约,那它的行为类似于一个空包装,因此,Promise.reslove()方法可以说是一个幂等方法。如下图所示:
这个幂等性会保留传入的期约的状态:
let p = Promise.reject();
let pp = Promise.resolve(p); // Promise <rejected>
p === pp // true
Promise.reject()
与Promise.resolve类似,Promise.reject()会实例化一个拒绝的期约并抛出一个异步错误(这个错误不能通过try/catch捕获,只能通过拒绝处理函数捕获)。下面两个期约实例其实是一样的:
let p1 = new Promise((resolve,reject) => reject(4));
let p2 = Promise.reject(4);
这个reject()函数没有幂等性一说,如果传入一个期约对象,那么这个对象会成为期约的(失败)结果(拒绝的理由)。
let p1 = Promise.resolve();
Promise.reject(p1); // Promise <rejected>: Promise <resolved>
期约的实例方法
Promise.prototype.then()
上文我们说到,期约是一种代替,它对真实结果进行了预测或预先的安排,但是直到该结果真正存在,这个预期才可能得到实现。到目前为止,我们已经有期约的结果了,实现预期就是调用期约实例的then()方法。
参数: then方法有最多接收2个函数参数,onResolved处理函数和onRejected处理函数。
当期约处于不同的状态时,会调用其中一个对应的方法。也就是根据不同的种类的结果,调用不同的方法,这个方法的参数为期约的结果。
返回值: 返回一个新的期约实例。
then方法中的两个处理函数都接收一个参数,这个参数为期约的结果值。我们可以使用期约的结果做一些操作。
let p1 = new Promise(resolve => setTimeout(resolve('成功'),1000))
p1.then(res => console.log(res))
// 成功
then方法返回一个新期约。这个期约是基于处理函数的返回值构建的。
处理程序的返回值会通过Promise.resolve()包装 来生成新期约:
- 调用
then时不传入处理程序:则会延续之前的期约状态和值。
let p1 = Promise.resolve('foo'); // Promise <resolve>: 'foo'
let p2 = p1.then(); // Promise <resolve>: 'foo'
p1 === p2 // false
- 如果有显示返回值,则
Promise.resolve会包装这个值:
let p1 = Promise.resolve('foo'); // Promise <resolve>: 'foo'
let p2 = p1.then(() => 'bar'); // Promise <resolve>: 'bar'
// 返回值是一个期约,则会保留期约的状态
let p1 = new Promise((resolve) => resolve());
let p2 = Promise.reject('far');
let p3 = p1.then(() => new Promise(() => {});); // Promise <pending>
let p4 = p1.then(() => Promise.reject()) // Promise <rejected>: nudefinded
// 抛出异常,则会返回拒绝期约
let p5 = p1.then(() => {throw 'baz'}); // Promise <rejected>: baz
let p1 = Promise.reject('foo'); // Promise <reject>: 'foo'
// 没有处理函数,则原样像后传
let p2 = p1.then(); // Promise <resolve>: 'foo'
// 有显示返回值则包装这个值。
let p3 = p1.then(null, () => {}) // Promise <resolved>: undefined
let p3 = p1.then(null, () => 'fbar') // Promise <resolved>: bar
// 返回值是一个期约,则会保留期约的状态
let p3 = p1.then(null, () => new Promise(() => {}) // Promise <pending>
let p3 = p1.then(null, () => Promise.reject()) // Promise <rejected>: nudefinded
// 抛出异常,则会返回拒绝期约
let p5 = p1.then(() => {throw 'baz'}); // Promise <rejected>: baz
Promise.prototype.catch()
.catch用来给Promise添加一个拒绝的处函数。其实是Promise.prototype.then(null, onRejected)的语法糖,两者等价。
实际上,catch() 方法内部会调用当前 promise 对象的then() 方法,并将 undefined 和 onRejected作为参数传递给 then()。调用后的值直接返回,所以本质还是Promise.prototype.then(null, onRejected),所以你可以根据这个本质,判断catch链式返回的结果。
好了,到目前为止,你已经掌握期约的大部分基础知识了,我们再来品读一下这句话:
定义: 期约是对尚不存在结果的一个替身。讲通俗一点就是:期约是预先对某件事情或结果进行预期,但它(指尚不存在的结果)并不存在。期约是一种代替,它对真实结果进行了预测或预先的安排,但是直到该结果真正存在,这个预期才可能得到实现。
了解了这些知识之后,你就可以轻易理解更多关于promise的东西,这里建议看mdn文档,很好理解了。
总结
学习路线:
- 首先了解promise的定义
- 了解Promise的状态和如何切换状态
- 理解Promise.resolve() 方法的幂等性
- 重点掌握then()方法会返回什么样的promise对象
- 最后看MDN的文档就能很好的理解其他方法
小结:
promise的方法都会返回新的promise对象,这是我们链式调用promise的基础。