Promise总结

Promise理解

Promise是javascript中进行异步编程的新解决方案

  1. 从语法上来说:Promise是一个构造函数
  2. 从功能上来说:Promise对象用来封装一个异步操作并可以获取其成功/失败的结果值。

异步编程:

  • 定时器
  • ajax
  • fs 文件操作(nodejs环境)
  • 数据库操作(nodejs环境)

好处:

  • 支持链式调用
    • 调用Promise构造函数会返回一个Promise对象,该对象可以调用Promise上相关的方法(then、catch、finally),调用这些方法也会返回一个已经解决的Promise实例,所以支持链式调用。
  • 解决回调地狱的问题
    • 回调地狱:不便于阅读、不便于异常处理
    • 回调地狱的产生主要源于:需要获取到异步操作的结果,并利用该结果进行新的异步操作,以此类推,会产生回调地狱的编码风格。
  • 获取异步操作结果更方便
    • 在Promise出现之前,对于异步操作,都是利用回调函数来进行操作的,利用回调函数获取异步操作的结果,并利用回调函数对结果进行业务处理,所以回调函数的编写必须存在于当前异步操作中,且异步操作的结果只限于当前回调函数,因此相比来说,不是那么的灵活。而使用Promise,通过返回的Promise实例对象,来对异步操作的结果更加自由的操作,并且回调函数可以在then/catch/finally任意地方进行编码处理结果。

Promise语法

promise的状态改变

promise的状态指的是:promise实例对象中的一个属性:【PromiseState】,一共有三个属性值

  • pending 未决定的,初始化时的默认值
  • resolved / fullfilled 成功
  • rejected 失败

promise的状态只可能发生以下几种情况:

  • pending => resolved
  • pending => rejected

且状态一旦发生变化,就不可逆。
例子:

let pro = new Promise(function (resolve, reject) {});
console.log(pro);

在浏览器环境中进行查看,node环境只能查看状态,所以无法看到对象的其他属性值

在这里插入图片描述

如何更改promise状态呢?

可以通过传给Promise构造函数的初始化函数中的参数resolve/reject来改变状态

  • resolve: pengding=>fulfilled/resolved
  • reject: pengding=> rejected
  • throw错误: pending=>rejected

promise对象的值

promise对象的值指的是:实例对象中的一个属性:【PromiseResult】,该属性存储的是异步任务成功/失败的结果。
能修改该属性的方法只有两个:

  • resolve
  • reject

通过以上方式修改【PromiseResult】的值,后期可以通过then/catch方法的调用获取到该属性的值。

promise工作流程

成功 执行resolve
失败 执行reject
new Promise--pending状态
执行异步操作
修改promise状态为resolved
修改promise状态为rejected
执行then方法中第一个回调函数
执行then方法中第二个回调函数或者执行catch方法中回调函数
以上方法执行完成之后 返回一个新的promise实例对象

Promise API

  • 初始化Promise
let pro = new Promise(excutor)

其中:excutor为执行器函数,执行器会在Promise内部立即 同步调用,而异步操在执行器中执行。

什么意思呢?

当js执行到当前行的时候,excutor函数内部有同步代码,则会立即执行,而如果遇到了异步操作的代码,则会放在任务队列中。

除此之外,执行器默认接收两个参数,一个是resolve函数,一个是reject函数,开发者可以利用这两个参数在异步任务成功/失败的时候进行调用。

  • Promise.prototype.then()

该方法会在异步任务执行完成,且异步任务中调用了resolve/reject,同时该任务处于任务队列中最头部位置时,进行调用。
接收两个参数:

  • onResolved:异步任务成功时的回调函数
  • onRejected:异步任务失败时的回调函数

该方法会返回一个新的Promise实例对象。

那么返回的新的Promise实例对象的状态以及值是如何指定的呢?

以下不管是成功还是失败回调函数都是一样的规则

  • 如果回调函数中抛出异常(throw),则返回一个失败的Promise实例对象,值为抛出异常的描述信息。
  • 如果回调函数中return一个非Promise对象值,则返回一个成功的Promise实例对象,值为return的值。
  • 如果回调函数中return一个Promise对象值,状态和值由Promise对象的状态和值决定。

注意:如果回调函数中既没有throw,也没有return一个值,那么返回的新的Promise实例对象状态依然是resolve,但是其值为undefined

例子:

let p = new Promise((resolve, reject) => {
    reject(12);
});

let re = p.then(value => {
    console.log(value);
}, reason => {
    return Promise.resolve('0')
});

console.log(re); // 【PromiseState】: resolve 【PromiseResult】: 0;

链式调用:
由于then方法是可以指定Promise实例中异步任务成功/失败时的回调函数,那也就是对任何的Promise实例对象都可以调用then方法对这两种状态进行处理。从以上的实例可以看出,then方法会返回一个新的Promise实例对象,所以我们可以通过链式调用来处理需要并联进行的操作。
比如:

let p = new Promise((resolve, reject) => {
    reject(12);
});
p.then(value => {
    console.log(value);
}, reason => {
    return Promise.resolve('0')
}).then(value => {
    console.log(value);// 0
}).then(value => {
    console.log(value);// undefined
});

  • Promise.prototype.catch()

该方法会在异步任务失败时进行调用,属于一个语法糖,其内部实现还是使用了then方法
该方法会返回一个新的已解决的Promise实例对象。

需要注意的是,以上对失败的处理(then的第二个参数/catch),都是异步处理的,不能使用同步的方法try/catch进行处理

  • Promise.resolve()

该方法属于Promise构造函数,而不属于Promise实例对象,调用该方法可以立即得到一个Promise的实例对象。该实例对象的状态采用以下规则确定:

  1. 如果传入的参数为 非Promise类型的对象,则返回的对象为成功状态的Promise实例对象
  2. 如果传入的参数为 Promise类型的对象,则返回的对象状态根据传入的参数的状态来决定。

比如:

const p = Promise.resolve(new Promise((resolve,reject)=>{
    reject('1');
}))

则p的【PromiseState】的值为: Rejected。

  • Promise.reject()

类似于Promise.resolve(),也是属于Promise构造函数的方法,但是这个方法,总是获得一个失败的Promise期约,且传递的值作为该方法的返回值,不管传递什么参数都是如此。它不同于Promise.resolve对参数类型不同返回的期约是不同的。

比如:传入一个已经解决的期约,返回的是一个失败的期约,且其值为已解决的期约。

let pro = Promise.reject(new Promise(function (resolve, reject) {
        resolve('ok');
}))

console.log(pro);

结果:

在这里插入图片描述

  • Promise.all()

是对多个期约作用的一个方法。
参数:期约的集合
返回状态(【PromiseState】):当且仅当所有期约都是已解决的期约,才返回已解决的期约。只要有一个期约为失败的期约,那么返回一个失败的期约。
返回的结果值(【PromiseResult】):当且仅当所有期约都是已解决的期约,结果值为所有已解决的结果值的集合。只要有一个期约为失败的期约,结果值为:最开始返回失败期约的结果值。

例子(失败案例):

let p1 = Promise.resolve('1');
let p2 = Promise.reject('ii');
let p3 = Promise.reject('ok');

let p4 = Promise.all([p1,p2,p3]);
console.log(p4);

结果:
在这里插入图片描述

例子(成功案例):

let p1 = Promise.resolve('1');
let p2 = Promise.resolve('ii');
let p3 = Promise.resolve('ok');

let p4 = Promise.all([p1,p2,p3]);
console.log(p4);

结果
在这里插入图片描述

  • Promise.race()

类似于Promise.all(),也是一个对多个期约作用的方法。
参数:期约的集合
返回状态(【PromiseState】):由第一个返回的期约的状态所决定。
返回值(【PromiseResult】):由第一个返回的期约的返回值所决定。

例子:

let p1 = new Promise(function (resolve, reject) {
        setTimeout(() => {
            resolve(1);
        }, 1000);
})

let p2 = Promise.reject(2);
let p3 = Promise.resolve(3);

let p4 = Promise.race([p1,p2,p3]);
console.log(p4);

结果:
在这里插入图片描述

由于第一个期约需要等待1s之后,才能返回,而第二个期约能够直接返回,所以期约状态为第二个的状态,期约返回值为第二个的返回值。

常见问题

  • 一个promise指定多个成功/失败回调函数,都会调用么?

当promise对象状态发生变化时,对应状态的回调函数都会被调用。
比如:

let p = new Promise((resolve, reject) => {
    resolve(12);
});

p.then(value => {
    console.log(value);
});

p.then(value => {
    console.log(value);
});

查看打印结果:
在这里插入图片描述

可以看到,当promise状态改变为成功时(resolve),指定的两个成功回调函数(then的第一个参数),都被调用了。


  • 改变promise状态和指定回调函数谁先谁后?
  1. 都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调。这里的指定回调指的是:初始化回调函数(不一定执行)
  2. 什么情况下哪个事件先发生呢?
  • 先改状态再指定回调

    1. 执行器函数中执行的是同步任务,同步任务直接调用了resolve/reject
    2. 延迟更长时间才调用then
  • 先指定回调再改变状态

    执行器函数中执行的异步任务。

从上面我们总结的情况可以看出:由于js执行机制的原因,先执行同步任务,遇到异步任务放到异步队列中,等同步任务执行完毕,再通过事件循环执行异步任务。所以如果是在同步任务中直接调用resolve/reject,状态就会立即改变,接着再初始化回调函数。但是如果是在异步任务中调用resolve/reject,那么异步任务会在同步任务之后执行,此时就会先初始化回调函数,再改变状态。

  1. 那什么时候获取的数据呢?
  • 如果先指定回调,当状态改变时,回调函数就会调用,获取到数据
  • 如果先改变状态,当指定回调函数的同时,回调函数而会被调用,得到数据。

  • promise异常传透

当使用promise的then链式调用时,可以在最后指定失败的回调。前面任何操作出了异常,都会传到最后失败的回调中处理。但是这里有个前提,如果一旦该异常出现后,在最后失败回调之前调用了失败回调,则该异常就会被捕获。

比如:

let p = new Promise((resolve, reject) => {
    reject(12);
});

p.then(value => {
    console.log(value);
}, reason => {
    console.log(reason);// 异常被捕获了
}).then(value => {
    console.log(value);
}).catch(reason => {
    console.log(1);
    console.log(reason); // 无法到达这里。
});

我们也可以统一处理异常,也即在最后添加失败回调。

let p = new Promise((resolve, reject) => {
    reject(12);
});

p.then(value => {
    console.log(value);
}).then(value => {
    console.log(value);
}).catch(reason => { // 这里也可以使用then的第二个参数
    console.log(reason); // 捕获到了异常
});

  • 中断Promise链

Promise链:利用Promise实例对象可以链式调用then/catch的特性所形成的链。
需求:当仅仅想执行当且回调函数,不想再执行之后的链式调用。
做法:返回一个pending状态的Promise实例对象。

let p = new Promise((resolve, reject) => {
    resolve(12);
});

p.then(value => {
    console.log(value);
    return new Promise(() => {});
}).then(value => {
    console.log(value);
}).catch(reason => {
    console.log(reason);
});

为什么返回pending状态的Promise实例对象可以实现呢?原因:Promise实例一旦发生变化(pending->resolve/reject),都会去调用与之对应的回调函数,也即总会去执行then/catch方法。而pending状态不会发生以上状况。

使用Promise封装异步操作

promise封装:fs模块

const fs = require('fs');
let promise = new Promise(((resolve, reject) =>  {
    fs.readFile('../html/test.html', (err, data) => {
        if (err) reject(err);
        resolve(data);
    })
}))

promise.then(data => {
    console.log(data.toString());
}, reason => {
    console.log(reason);
})

node中util模块里面的函数:promisify
该函数的作用:将异步函数自动进行Promise封装,并且返回一个Promise实例,这样就不需要以上方式手动封装。
使用方法:

  • 引入util模块
const util = require('util');
  • 将待封装的异步操作传入到promisify函数中
let mineReadFile = util.promisify(fs.readFile);

注意:此处不需要传入异步操作的参数

  • 使用已封装的函数,使用方法和原来异步操作函数一致(只是对其进行了封装而已)
mineReadFile('../html/test.html')
   .then(data => {
       console.log(data.toString());
   }, reason => {
       console.log(reason)
   })
  • 完整的代码
const fs = require('fs');
const util = require('util');

let mineReadFile = util.promisify(fs.readFile);

mineReadFile('../html/test.html')
    .then(data => {
        console.log(data.toString());
    }, reason => {
        console.log(reason)
    })

仿写PromiseAPI

class Promise {
    constructor(executor) {

        this.PromiseState = 'pending';
        this.PromiseResult = null;

        // 保存异步任务的回调函数
        this.callbacks = [];

        // 保存当前实例的上下文
        const self = this;

        function resolve(data) {
            // 判断状态,保证状态只会被修改一次
            if (self.PromiseState !== 'pending') return;
            // 1. 改变状态
            self.PromiseState = 'fulfilled';
            // 2. 改变值
            self.PromiseResult = data;

            // 处理异步任务的结果的回调
            // 异步处理结果
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onResolved(data);
                })
            })
        }

        function reject(data) {
            // 判断状态,保证状态只会被修改一次
            if (self.PromiseState !== 'pending') return;
            // 1. 改变状态
            self.PromiseState = 'rejected';
            // 2. 改变值
            self.PromiseResult = data;

            // 处理异步任务的结果的回调
            // 异步处理结果
            setTimeout(() => {
                self.callbacks.forEach(item => {
                    item.onRejected(data);
                })
            })
        }

        // 处理throw的方法
        try {
            executor(resolve, reject);
        } catch (e) {
            // 改为失败的状态
            reject(e);
        }
    }

    // 添加then方法
    then(onResolved, onRejected) {
        // 保存上下文
        const self = this;
        // 判断回调函数参数,如果不是函数,则给其一个默认值,实现异常穿透,等效于帮用户将异常传递到最后的异常捕获的回调函数中进行处理
        // 如果没有这一步,当该实例reject/throw的时候,且失败处理放在最后,那么之前的then方法都会因为onRejected为undefined而抛出异常
        // 虽然最后还是会被catch捕获,但是失败值会是:onRejected is not a function,而不是期待输出的失败值。
        if (typeof onRejected !== 'function') {
            onRejected = reason => {
                throw reason;
            }
        }

        // 处理如果then方法中什么参数都没有传递的情况,防止报错。注意:错误的创建都需要用户去定义,而不是源码本身去创建用户不知道的错误。
        if (typeof onResolved !== 'function') {
            onResolved = value => value;
        }
        // 返回一个新的Promise实例
        return new Promise((resolve, reject) => {
            // 封装函数
            function callback(type) {
                // 处理throw的情况
                try {
                    // 获取回调的执行结果
                    let result = type(self.PromiseResult);

                    // 如果是Promise实例,新的Promise实例对象则需要根据当前Promise实例状态来指定
                    if (result instanceof Promise) {
                        // 由于result是Promise实例对象,所以也可以调用then方法,根据result实例对象的状态会调用对应的回调函数
                        result.then(value=> {
                            resolve(value);
                        }, reason => {
                            reject(reason);
                        })
                    } else {
                        // 不是Promise实例对象,则直接将新的Promise实例对象状态设为成功
                        resolve(result);
                    }
                } catch (e) {
                    // 如果抛出异常,则将状态改为失败【rejected】
                    reject(e);
                }
            }
            // 以下只针对同步方式
            // 成功执行回调,并把成功结果传入
            if (this.PromiseState === 'fulfilled') {
                // then方法是异步任务的特性
                setTimeout(() => {
                    callback(onResolved);
                })
            }
            // 失败执行回调,并把失败结果传入
            if (this.PromiseState === 'rejected') {
                setTimeout(() => {
                    callback(onRejected);
                })
            }

            // 针对异步任务,保存回调函数,待状态发生变化的时候去执行
            if (this.PromiseState === 'pending') {
                // 针对给该Promise实例调用多个then的情况,将其放在数组里面,之后状态改变的时候,逐个去执行
                this.callbacks.push({
                    onResolved: function () {
                        callback(onResolved);
                    },
                    onRejected: function () {
                        callback(onRejected);
                    }
                })
            }
        })
    }

    // 添加catch方法
    catch(onRejected) {
        return this.then(undefined, onRejected);
    }

    // 添加resolve方法
    static resolve(type) {
        return new Promise((resolve, reject) => {
            // Promise实例对象,则根据实例对象状态决定
            if (type instanceof Promise) {
                type.then(value => {
                    resolve(value);
                }, reason => {
                    reject(reason);
                })
            } else {
                // 非Promise实例对象,则返回成功的Promise实例对象
                resolve(type);
            }
        })
    }

    // 添加reject方法
    static reject(value) {
        return new Promise((resolve, reject) => {
            reject(value);
        });
    }

    // 添加all方法
    static all(promises) {
        if (!Array.isArray(promises)) {
            // 如果不是数组,则抛出异常
            throw new Error(`${typeof promises} is not iterable (cannot read property Symbol(Symbol.iterator))`);
        }

        return new Promise((resolve, reject) => {

            // 声明一个标识为,表示实例成功的个数,当前仅当count === promises.length的时候,才会将返回的Promise实例对象状态改为成功
            // 只要promises中有一个对象的状态改为失败,则立即改变当前的Promise实例对象为失败
            let count = 0;
            // 保存成功实例对象的返回的结果
            let arr = [];
            for (let i = 0; i < promises.length; i++) {

                // 每个Promise实例都有一个then方法,可以通过then方法获取到当前实例的状态变化
                promises[i].then(value => {
                    // 当前实例变为成功,必然会走这
                    // 只要成功,则增加1 表明成功的个数
                    count++;
                    // 保存成功实例对象的返回结果,注意顺序问题
                    arr[i] = value;
                    if (count === promises.length) {
                        // 只要实例对象全部都成功,才改变实例对象状态,且把保存的所有实例结果的数组作为当且实例对象的值传入
                        resolve(arr);
                    }
                }, reason => {
                    // 只要失败,直接改变当且对象的状态为失败
                    reject(reason);
                })
            }
        })
    }

    // 添加race方法
    static race(promises) {
        if (!Array.isArray(promises)) {
            // 如果不是数组,则抛出异常
            throw new Error(`${typeof promises} is not iterable (cannot read property Symbol(Symbol.iterator))`);
        }

        return new Promise((resolve, reject) => {
            // 只要实例对象中有一个状态发生改变,则直接改变当前实例对象的状态
            promises.forEach(item => {
                item.then(value => {
                    resolve(value);
                }, reason => {
                    reject(reason);
                })
            })
        })
    }
}

async和await语法糖

  • async关键字

通过在函数声明的前面添加async关键字,会将函数返回的结果封装为一个Promise实例对象。
返回的Promise实例对象的状态和值的规则与then方法返回的实例对象的状态和值的规则一致。

  • 如果return一个非Promise实例对象值,则返回的Promise实例对象的状态为成功,且值为return 的值。
  • 如果return一个Promise实例对象,则返回的Promiseshi里对象的状态和值与return的对象的状态和值一致。
  • 如果抛出异常,throw,则返回的Promise实例对象的状态为失败,且值为异常信息。

  • await表达式

作用:获取Promise实例对象成功的结果

  • await右侧的表达式一般为Promise实例对象,但也可以是其他的值
  • 如果表达式是Promise实例对象,await返回的是Promise实例对象成功的值
  • 如果表达式时其他的值,则直接将此值作为await的返回值。

注意:

  • await必须写在async函数中,但async函数中可以没有await
  • 如果await的右侧的Promise实例对象状态为失败,就会抛出异常,需要使用try…catch捕获异常,并且在catch中获得失败的值。

  • async和await结合使用

例子:

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

async function main() {

   try {
      let f1 = await mineReadFile('../html/test.html');
      let f2 = await mineReadFile('../html/test2.html');
      console.log((f1 + f2));
   } catch (e) {
      console.log(e);
   }
}

main();

可以看到使用async/await语法糖可以不用手动调用then方法获取到成功/失败的结果,更加的方便。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值