你会学Promise吗?

272 阅读8分钟

Promise怎么学?

我们知道Promise是ES6新增加的用来解决异步问题的,那么我们当了解到这个新事物之后我们改如何去学习他呢?

根据它的用法new Promise,我们知道Promise肯定是一个构造函数,那么构造函数上面肯定会有自己的静态方法,也会在原型上拓展自己的方法,以供自己的实例调用,那么我们就可以在浏览器的控制台打印一下,看看我们需要学习它的方法有哪些?

打印Promise这个函数对象(函数分三种角色:普通函数、构造函数、对象---此知识点会在后续的文章中发布):

打印Promise的原型:

打印Promise的实例:new Promise(()=>{}):

这里强调一下:new Promise的时候必选要给他传递一个回调函数:否则会报错!,

我们可以看到,当我们实例化Promise后他的实例上会有两个属性,

PromiseStatus:状态,

PromiseValue:值。(下面会讲)

Promise基基本使用方法:

由一个面试题引出Promise的基本用法:

设计一个等待函数,等待n时间后执行要做的事情
function delay(callback,interval = 1000){
  return new Promise((resolve)=>{
       let timer = setTimeout(()=>{
       clearTimeout(timer);
        timer=null;
         resolve()
       })
  },interval)
}
delay(1000).then(()=>{
    console.log(1);
    return delay(2000);
}).then(()=>{
    console.log(2);
    return delay(3000);
}).then(()=>{
    console.log(3)
})
Promise是用来管控异步编程的,new Promise本身不是异步的,执行它的时候会立即把executor函数执行,(只不过我们在executor中管控一个异步操作)
  • resolve/reject:传递给executor的参数,(参数值都是函数)
  • promise的初始状态是"pedding", 初始值是undefined。
  • resolve([value]) 修改promise状态为resolved/fulfilled成功状态,并且改变其值为[value],
      [[PromiseStatus]]: "fulfilled"
      [[PromiseValue]]: value
  • reject相反

Promise对象上的方法及使用

我们在上图中可以看到在Promise函数对象上有好多的属性方法可以供我们使用,我们最常用的有resolve和reject以及all和race方法。

那么我们看看他们的使用方法:

【resolve】:很简单,执行它时:Promise.resolve([value]),会返回一个成功态(fulfilled)的Promise实例,并且promise的PromiseValue会被改变为:value;

【reject】: 执行它时:Promise.reject([reason]),会返回一个失败态(rejected)的Promise实例,并且promise的PromiseValue会被改变为:reason;

all: 我们需要给他传递一个每一项是Promise的数组,返回出的结果是一个Promise实例,通过.then方法可以接到返回出的值,也是一个数组,并且数组中每一项都是传入的Promise实例的输出结果。

【需要注意1。如果数组中有常量项,那么返回的结果中会原值显示这一项】

【需要注意2,数组中只要有一个promise实例返回的结果是失败的整个就会失败,失败的原因就是这个实例失败的原因】

race: 传入的参数也是一个promise的数组,执行时,只要有一个Promise变为成功态就会返回结果;

【Promise承诺】就是指的是当executor函数中resolve()和reject()方法执行后一定会执行实例上的then函数,这就是信任问题;

Promise原型上的方法及使用

then: 这个方法会返回一个新的Promise实例,并且这个实例的状态有一下集中情况决定:

【1】回调函数中的代码是否报错决定的,如果有报错,那么返回的就是失败态的promise实例,[[PromiseValue]]的值就是失败的原因;
【2】如果没有报错那么就会返回成功态的promise实例,[[PromiseValue]]就是回调函数return 出的值;
【3】如果回调函数又返回一个新的promise实例,那么这个promise实例的状态即是then返回的Promise的状态,[[PromiseValue]]值也同理;

.catch: catch====then(null,function(){});

其实catch是由then方法的一个特性:顺延性诞生的;

【then方法的顺延性】:当我们在执行then后,如果在第一个promise中是返回的rejected状态的实例,但是在then方法里面没有第二个回调函数也就是null,那么这个错误的原因不会打印,因为没有承载它的回调函数,如果在.then.then方法里面有第二个回调函数,那么会在这个回调函数中拿到错误的结果。当然确实第一个参数函数也是如此;因此catch也就是在then的后面接收错误的,p1.then(()=>{},null).then(()=>{},null).catch(/在这里捕获到错误/)

Promise源码手写:

基本使用:

function feiPromise(executor) {    
    let _this = this;//保存this
    if (typeof executor !== 'function') {
        throw new TypeError('MyPromise resolver ' + executor + ' is not a function');    }
    this.PromiseValue = null;//保存值
    this.PromiseStatus = 'pedding';//保存状态
    //改变状态和值,并执行then中的参数函数;
    function change(status, value) {
        if (_this.PromiseStatus !== 'pedding') return;//状态一经改变就不允许再改变
        _this.PromiseStatus = status; 
       _this.PromiseValue = value;
    }
    //用try catch 捕获立即执行函数executor执行中的错误,有错误的话直接reject抛出。
    try {
        executor(function (value) {
            change('fulfilled', value);
        }, function (reason) {
            change('rejected', reason);
        })    } catch (error) {
        change('rejected', error.message);
    }}
//测试调用:====>
//首先不调用改变状态的方法
let p1 = new Promise((resolve,reject)=>{});Ï
console.log(p1)//->>feiPromise {PromiseValue: null, PromiseStatus: "pedding", resolveFunc: ƒ, rejectFunc: ƒ}
//调用改变状态的方法:
let p1 = new Promise((resolve,reject)=>{resolve(111});Ïconsole.log(p1)//->>feiPromise {PromiseValue: 888, PromiseStatus: "fulfilled", resolveFunc: ƒ, rejectFunc: ƒ}

补充方法:

resolve和reject方法:

feiPromise.resolve = function (value) {
    return new feiPromise((resolve) => { resolve(value) })//直接将改变状态后的实例返回
}
feiPromise.reject = function (reason) {
    return new feiPromise((_, reject) => { reject(reason) })//直接将改变状态后的实例返回
}

.then方法:

我们注意到当我们执行then的时候我们是可以接到通过resolve([value])或reject([reason])方法改变的PromiseValue的值的,所以我们在内部实现的时候:当我们改变状态和值得时候也就是执行change方法的时候我们确保then方法里面的回调函数是已经注册好的,这样我们在可以执行其中的回调函数,这样在then的回调函数里面我们才可以拿到对应的值:因此实现这个功能我们采用定时器,将执行then回调函数的方法写在定时器里面。

因次我们修改change方法:

function feiPromise(executor) {    let _this = this;
    //参数校验    if (typeof executor !== 'function') {
        throw new TypeError('MyPromise resolver ' + executor + ' is not a function');
    }
     // 设置实例的私有属性
    this.PromiseValue = null;//保存值
    this.PromiseStatus = 'pedding';//保存状态
    this.resolveFunc = function () { };//保存then回调函数的第一个参数
    this.rejectFunc = function () { };//保存then回调函数的第二个参数
    // 修改实例的状态和value:只有当前状态为pending才能修改状态
    function change(status, value) {
        if (_this.PromiseStatus !== 'pedding') return;
        _this.PromiseStatus = status;
        _this.PromiseValue = value;
        // 通知基于.then注入的某个方法执行(异步的) 
       var promiseTimer = setTimeout(() => {//定时器保证调用时,then中的回调函数已经注册好
            clearTimeout(promiseTimer);
            promiseTimer = null;
            //执行对应的方法,并把参数传递PromiseValue
            _this.PromiseStatus == 'fulfilled' ? _this.resolveFunc.call(_this, _this.PromiseValue) : _this.rejectFunc.call(_this, _this.PromiseValue)        }, 0);    }    try {        executor(function (value) {            change('fulfilled', value);        }, function (reason) {            change('rejected', reason);        })    } catch (error) {        change('rejected', error.message);    }}

feiPromise.prototype.then = function (resolveFunc, rejectFunc) {   
     //将对应的方法存储到实例上
     this.resolveFunc = resolveFunc;
     this.rejectFunc = rejectFunc;
}

then的特性【1】:顺延机制,既然没有如果没有对应的回调函数的话它会顺延到下面的then,

那么想要实现我们就可以在then的内部,检测是否传递了两个参数,没有传递的话我们就默认给他赋值一个对应的Promise;

function feiPromise(executor) {
    let _this = this;
    //参数校验 
   if (typeof executor !== 'function') {
        throw new TypeError('MyPromise resolver ' + executor + ' is not a function');
    }
     // 设置实例的私有属性
    this.PromiseValue = null;//保存值 
    this.PromiseStatus = 'pedding';//保存状态
    this.resolveFunc = function () { };//保存then回调函数的第一个参数
    this.rejectFunc = function () { };//保存then回调函数的第二个参数
    // 修改实例的状态和value:只有当前状态为pending才能修改状态
    function change(status, value) {
        if (_this.PromiseStatus !== 'pedding') return;
        _this.PromiseStatus = status;
        _this.PromiseValue = value;
        // 通知基于.then注入的某个方法执行(异步的)
        var promiseTimer = setTimeout(() => {//定时器保证调用时,then中的回调函数已经注册好
            clearTimeout(promiseTimer);
            promiseTimer = null;
            //执行对应的方法,并把参数传递PromiseValue
            _this.PromiseStatus == 'fulfilled' ? _this.resolveFunc.call(_this, _this.PromiseValue) : _this.rejectFunc.call(_this, _this.PromiseValue)
        }, 0);    
       }
    try {
        executor(function (value) {
            change('fulfilled', value);
        }, function (reason) {
            change('rejected', reason);
        })    
      } catch (error) { 
       change('rejected', error.message);
    }}

feiPromise.prototype.then = function (resolveFunc, rejectFunc) {   
     //如果没有传递参数
     if (typeof resolveFunc !== 'function') {        resolveFunc = function (value) {            return feiPromise.resolve(value)        }    }    if (typeof rejectFunc !== 'function') {        rejectFunc = function (reason) {            return feiPromise.reject(reason);        }    }

特性【2】:then会返回一个新的promise实例,这个实例的状态和值有三种情况:

function feiPromise(executor) {
    let _this = this;
    //参数校验 
   if (typeof executor !== 'function') {
        throw new TypeError('MyPromise resolver ' + executor + ' is not a function');
    }
     // 设置实例的私有属性
    this.PromiseValue = null;//保存值 
    this.PromiseStatus = 'pedding';//保存状态
    this.resolveFunc = function () { };//保存then回调函数的第一个参数
    this.rejectFunc = function () { };//保存then回调函数的第二个参数
    // 修改实例的状态和value:只有当前状态为pending才能修改状态
    function change(status, value) {
        if (_this.PromiseStatus !== 'pedding') return;
        _this.PromiseStatus = status;
        _this.PromiseValue = value;
        // 通知基于.then注入的某个方法执行(异步的)
        var promiseTimer = setTimeout(() => {//定时器保证调用时,then中的回调函数已经注册好
            clearTimeout(promiseTimer);
            promiseTimer = null;
            //执行对应的方法,并把参数传递PromiseValue
            _this.PromiseStatus == 'fulfilled' ? _this.resolveFunc.call(_this, _this.PromiseValue) : _this.rejectFunc.call(_this, _this.PromiseValue)
        }, 0);    
       }
    try {
        executor(function (value) {
            change('fulfilled', value);
        }, function (reason) {
            change('rejected', reason);
   })    
      } catch (error) { 
       change('rejected', error.message);
    }}

feiPromise.prototype.then = function (resolveFunc, rejectFunc) {   
    if (typeof resolveFunc !== 'function') { 
       resolveFunc = function (value) {
            return feiPromise.resolve(value)
        }    } 
   if (typeof rejectFunc !== 'function') {
        rejectFunc = function (reason) {
            return feiPromise.reject(reason);
        }    }
    return new feiPromise((resolve, reject) => {
        this.resolveFunc = function (value) { 
           try {
                var x = resolveFunc(value);
                x instanceof feiPromise ? x.then(resolve, reject) : resolve(x);//x.then(resolve, reject) :其实是相当于又执行了一次.then,然后走的三目的第二个条件
            } catch (error) { 
               reject(error);
            }
        }; 
       this.rejectFunc = function (reason) {
            try {
                var x = rejectFunc(reason);
                x instanceof feiPromise ? x.then(resolve, reject) : resolve(x);//x.then(resolve, reject) :其实是相当于又执行了一次.then,然后走的三目的第二个条件
            } catch (error) {
                reject(error);
            }
        }    })

all方法实现:

feiPromise.all = function (promiseArr) {
    var i, len = promiseArr.length, values = [], index = 0;
    return new feiPromise((resolve, reject) => {
        for (; i < len; i++) {
            (function (i) {
                var item = promiseArr[i]; 
               !(item instanceof feiPromise) ? feiPromise.resolve(item) : null;
                item.then((value) => {
                    index++; 
                   values[i] = value;
                    if (index >= len) {
                        resolve(values) 
                   } 
               }).catch((reason) => { reject(reason) })
            })(i)
        }    })}