ES6标准入门 学习笔记 (07) —— Promise篇

70 阅读7分钟

引入

系统学习ES6各种特性,了解背后的原理。

本章记录Promise

笔记

1.1 Promise的含义

Promise是异步编程的一种解决方案,比传统的回调函数、事件更加合理且强大。最早由社区提出并实现,ES6将其写进了语言标准并统一了用法,原生提供Promise对象。

Promise是一个容器,其中保存着某个未来才会结束的事件(异步操作)的结果

Promise对象有三种状态:

  1. Pending 进行中
  2. Fulfilled 已成功
  3. Rejected 已失败

只有异步操作的结果可以决定当前是哪一种状态,一旦状态改变(只可能从Pending变为Fulfilled或Rejected)后就不再变化,此后任何时候都能从中得到相应的结果。

这与事件不同,事件的特点是在发生时错过了,再去监听是得不到结果的。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套回调(回调地狱),此外Promise对象提供的解构也使得控制异步操作更加容易。

Promise也有一些缺点:

  1. 无法取消Promise,一旦新建就会立即执行
  2. 若不设置回调函数(.catch()等操作),Promise内抛出的错误不会反应到外部
  3. 处于Pending状态时,无法得知当前进展到哪一阶段(刚刚开始还是即将完成)

1.2 基本用法

Promise对象为构造函数,其接受一个函数作为参数来生成Promise实例,该函数有两个参数resolvereject,是两个函数,作用分别是将该Promise实例的状态由Pending转换为Fulfilled和Rejected

let pro = new Promise((resolve, reject)=>{
    // 异步操作...
    // 注意下列代码应当在异步操作的 回调 中执行
    if (/*xxx异步操作完成后,成功了*/){
        resolve(val);
    }else{
        reject(err);
    }
});

Promise实例生成后可以通过实例的then方法分别指定Resolved和Rejected状态的回调函数,其中Rejected回调函数是可选的,这两个函数都接受Promise对象传出的值(成功的值、失败的值)为参数。

注意:Rejected回调函数相当于抛出错误,需要有错误回调进行拦截才能进行处理,否则会被忽略

与传统try catch不同,错误不会被传递到外层代码中,虽然浏览器会打印该错误,但外部代码不会受到影响

pro.then(val=>{}, err=>{});

注意:新建Promise实例时,传入构造函数的函数内容会立即执行!而且是同步(阻塞进程的)操作

(所以不要把一堆同步操作放到Promise实例的构造里,没有什么意义)

let pro = new Promise((resolve, reject)=>{
    console.log(1)
    resolve();
}).then(res=>console.log(3))

console.log(2)
//输出 1 2 3

then方法中的内容会在所有同步任务结束后再执行,所以依次输出1 2 3

注意:调用resolve(), reject()不会终结参数函数的运行

new Promise(resolve=>{
    resolve(1);
    console.log(2);
}).then(res=>console.log(res));
\\ 输出 2 1

因为then中的代码会在本轮同步任务结束后才执行,所以还是会先输出2

一般来说执行完resolve或者reject方法后,该Promise的任务就完成了,应当终止函数的运行,所以可以在其见面加上return,避免意外情况。如:return resolve(1);

但是注意,resolve()后再抛出错误,Promise的状态不会改变为已失败,并且会忽略抛出的错误,因为Promise状态一经改变就不能再更改

1.3 Promise.prototype.then()

Promise实例具有then方法,该方法返回的是一个新的Promise实例,该实例和前一个实例不一样

新的Promise实例是一个状态由then方法内回调函数决定的Promise对象:

  • 返回了值或者没有返回任何值,新Promise对象为已成功,并且值为该值或者undefined
  • 抛出错误,新Promise对象为拒绝状态,并且将该错误作为值
  • 返回已成功的Promise,新Promise对象也是已成功,那个Promise的值作为新Promise对象的值
  • 返回已失败的Promise,新Promise对象也是已失败,那个Promise的值作为新Promise对象的值
  • 返回Pending的Promise,新Promise对象也是Pending,并且它的终态与那个Promise的终态相同,最终值也相同

由于then方法返回新Promise对象,所以可以进行链式调用

pro.then(res=>someAsyncFunc(res))
    .then(res=>someAsyncFunc(res))
    .then(res=>console.log(res))
    
\\此处then方法中使用箭头函数,返回了someAsyncFunc(),也就是返回PendingPromise

1.4 Promise.prototype.catch()

Promise.prototype.catch(rejection)Promise.prototype.then(null, rejection)的别名

因为Promise对象的错误具有冒泡性质,会一直向后传递,直到被捕获为止(但不会传递到外层)

所以实际应用中往往使用.then(success).catch(rejection)来接收成功与失败回调,没有成功或者失败时会跳过对应的回调处理。

pro.then(res=>someAsyncFunc(res))
    .then(res=>someAsyncFunc(res))
    .then(res=>console.log(res))
    .catch(err=>console.log(err))
//此处的catch可以处理之前任意一处发生的错误(发生错误后其他的then不会执行)

注意,catch()返回的还是一个Promise对象(和then一样),因此后面还可以接着调用then等等

1.5 Promise.all()

该方法用于将多个Promise实例包装成一个新的Promise实例。

该方法接受一个可迭代对象,如数组,作为参数。

let p = Promise.all([p1, p2, p3])

其中p1, p2, p3都是Promise实例,若不是则会调用Promise.resolve()将其转换为Promise实例

而p的状态由p1, p2, p3决定:

  1. 当三个Promise对象都变为Fulfilled时,p变为Fulfilled,值为三者值组成的数组
  2. 但三者中任意一个成为Rejected,p变为Rejected,值为三者中第一个成为Rejected的值

注意:若其中的Promise对象自己定义了catch()方法拦截错误,那么错误不会被传递到此处,catch返回的也是一个新的Promise作为all方法的参数内容,所以无影响

1.6 Promise.race()

该方法和all()很相似,只是新Promise实例的状态与参数中状态的关系不同

let p = Promise.race([p1, p2, p3])

只要p1, p2, p3中有一个实例率先改变状态,那么p的状态就跟着改变,并获得那个实例的值。

应用技巧:超时错误

const p = Promise.race([
    fetch('I resource-that-may-take- a - while'),
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000) //5s后抛出错误
    })
])

p.then(res=>console.log(res))
.catch(err=>console.log(err))

//若5s内fetch结束,则p的状态会因为setTimeout抛出的错误而改变

1.7 Promise.resolve()

该方法可以将现有对象转化为Promise对象,例子如下

let p = Promise.resolve('foo');
\\等价于
let p = new Promise(resolve=>resolve('foo'));
  1. 参数是Promise对象时,该方法不做任何修改,直接返回原对象
  2. 参数是thenable(即具有then方法的对象),该方法会先将其转化为Promise对象然后立即执行其then方法
let thenable = {
    then: (resolve, reject)=>{
        console.log(1)
        resolve(2);
    }
}
let p =Promise.resolve(thenable)
// 先输出1,然后p由Pending转化为Fulfilled且值为2
  1. 参数是其他情况,返回一个新的Promise实例,状态为Fulfilled,值为参数本身(当无参数时,值为undefined)

1.8 Promise.reject()

该方法也返回一个新的Promise实例,状态为Rejected

但是不同于Promise.resolve,不论参数是什么类型,该方法都会把参数原封不动的作为新Promise对象的值

1.9 有用的附加方法

1.9.1 done()

无论Promise对象的链式调用以then方法还是catch方法结尾,只要最后一个方法内再次抛出错误,外部代码都无法将其捕获。

为此可以实现一个done方法,其总是处于链式调用的最后一个,既能作为最后一个then方法使用,又能保证向外抛出最终的错误

Promise.prototype.done = function (onFulfilled, onRejected) {
    this.then(onFulfilled, onRejected) //此处的this指向调用done方法的对象
        .catch(function (err) { //实现关键:额外添加一个catch用来抛出可能出现的错误
            // 抛出一个全局错误
            setTimeout(() => { throw err }, 0);
        });
};

1.9.2 finally()

该方法用于指定不管Promise实例最终状态如何都会执行的操作。

Promise.prototype.finally = function (callback) {
    let P = this.constructor; //构造函数,指向原生Promise对象
    // 并且将callback()返回值转化为Promise对象返回,从而续接到then链式调用中
    return this.then(
        val=>P.resolve(callback()).then(()=>val),
        err=>P.resolve(callback()).then(()=>{throw err})
    );
};