Promise实现及理解

别让平淡的生活,毁了对未来的向往❤


1.promise的基本结构

promise必须接受一个函数( handle ) 作为参数,这个函数有两个参数:resolve,reject,且这两个参数本身也是函数。

let promise = new Promise((resolve, reject) => {
    resolve('success');  //reject('fail')
});
promise.then((res)=>{
  console.log(res); // 输出:success
},(err)=>{
  console.log(err); // 上面如果执行reject('fail'),这里就输出:fail
});

2.promise的状态和值

(1)promise有三个状态
  • Pending( 进行中 )
  • Fulfilled( 已成功 )

  • Rejected ( 已失败 )

状态只能由 **Pending--> Fulfilled** 或着 **Pending --> Rejected** 两种状态变更,变更之后状态**锁定**了,就不会再变更了。
(2)promise返回两种状态
  • resolve(成功 ):将Promise对象的状态从 Pending(进行中) 变为 Fulfilled(已成功)

  • reject(失败):将Promise对象的状态从 Pending(进行中) 变为 Rejected(已失败)

resolve 和 reject 函数都可以传入任意类型的值作为实参,表示 Promise 对象成功(Fulfilled)和失败(Rejected)的值。

(3)promise的值

Promise的值是指状态改变时传递给回调函数的值。

promise的参数handle函数,包含 resolve 和 reject 两个参数,它们是两个函数,可以用于改变 Promise 的状态和传入 Promise 的值。

(a) . resolve 传入的 ‘success’ ,就是 promise 的值。

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success');
  }, 1000)
});

(b). promise的 then方法返回一个promise。

let promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 1000)
})
promise2 = promise1.then(res => {
    return new Promise((resolve,reject)=>{ 
        resolve('hello world'); 
    })
})
promise2.then(res => {
  console.log(res) //1秒后打印出:hello world
});
(4)实现一个Promise类
  • 执行状态: status ;可以有 pending,resolved,rejected三个状态。

  • 执行结果: value;返回成功时,传递进来的值由value保存。

  • 失败原因: reason;返回失败时,失败的原因由reason保存。

  • 两个方法: resolve方法,reject方法; 来进行状态值得改变及成功或失败原因的保存。

  • 一个方法: then方法;根据不同状态,执行不同的函数,返回不同的结果。

  • 错误处理: try-catch;能够捕获错误,并且处理错误。

//创建一个Promise类
class Promise { 
    constructor(executor) {
        this.status = 'pending'; // 执行状态: 初始默认状态为pending(只有状态为pending才能转换状态)
        this.value = undefined;  // 执行结果:默认赋值为undefined
        this.reason = undefined; // 失败原因:默认赋值为undefined
        let resolve = (value) => {
            if (this.status === 'pending') { 
                this.value = value; //将传递进来的的值赋给value保存
                this.status = 'resolved'; //将状态设置成resolved
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') {
                this.reason = reason; //将传递进来的失败原因赋给reason保存
                this.status = 'rejected'; //将状态设置成rejected
            }
        }
        // 当代码出现错误的情况下,我们需要能够捕获错误,并且处理错误
        try{ 
            executor(resolve, reject); //默认执行executor
        }catch(e){
            reject(e);//如果发生错误,将错误放入reject中
        }
    }
    then(onFulfilled, onRejected) {  //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数
        if (this.status === 'resolved') { 
            onFulfilled(this.value); //如果状态是resolved,执行成功的resolve,并将成功后的值传递过去
        }
        if (this.status === 'rejected') {
            onRejected(this.reason); //如果状态是rejected,执行失败的reject,并将失败原因传递过去
        }
    }
}

以上实现会有一个问题,如果promise内部使用setTimeout去调用回调函数时,我们会发现上面的代码在控制台完全没有反应,实际上是因为我们没有处理这种延迟调用的情况。

(5)setTimeout 调用问题
  • 成功函数数组: successStore[ ];成功resolve状态时,依次调用数组中函数。

  • 失败函数数组: failStore[ ];失败reject状态时,依次调用数组中函数。

  • then方法:新增pending状态的判断,成功的函数存放到successStore数组里,失败的函数存放到fillStore数组中。

let promise = new Promise((resolve, reject) => {
    setTimeout(() =>{
        resolve('success');  //reject('fail')
  },1000);
});
promise.then((res)=>{
  console.log(res);
},(err)=>{
  console.log(err);
});

解决方法:【在上述代码基础上完善,如下代码】

class Promise { 
    constructor(executor) {
        ...此处略去部分代码
        this.successStore = [];  //定义一个存放成功函数的数组
        this.failStore = [];  //定义一个存放失败函数的数组
        let resolve = (value) => {
            if (this.status === 'pending') { 
                ...此处略去部分代码
                this.successStore.forEach(fn => fn()); //依次执行数组中的成功函数
            }
        }
        let reject = (reason) => {
            if (this.status === 'pending') { 
                ...此处略去部分代码
                this.failStore.forEach(fn => fn()) //依次执行数组中的失败函数
            }
        }
       ...此处略去部分代码
    }
    then(onFulfilled, onRejected) { //等同于es5的Promise.prototype.then 当调用then的时候,根据状态,来执行不同的函数
        ...此处略去部分代码
        if (this.status === 'pending') { //此处增加一种状态判断
            this.successStore.push(() => { //当状态为pending时将成功的函数存放到数组里
                onFulfilled(this.value);
            })
            this.failStore.push(() => { //当状态为pending时将失败的函数存放到数组中
                onRejected(this.reason);
            })
        }
    }
}

3.promise的then方法详解

Promise 对象的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected);
(1)参数可选
  • 如果参数不会函数,则忽略

  • 如果参数为函数:onFulfilled在promise状态变为成功时调用,onRejected在promise状态变为失败时调用。

(2)多次调用
  • 当 promise 成功状态时,所有 onFulfilled 需按照其注册顺序依次回调。

  • 当 promise 失败状态时,所有 onRejected 需按照其注册顺序依次回调。

(3)返回
  • then 方法必须返回一个新的 promise 对象
promise2 = promise1.then(onFulfilled, onRejected);
  • then 方法可以链式调用
promise1.then(onFulfilled1, onRejected1).then(onFulfilled2, onRejected2);
(4)链式调用
a. 在then方法的回调里返回一个普通值。

无论是成功还是失败的回调,都会进入到下一个then()的成功态里。

let promise = new Promise((resolve, reject) => {
    resolve('success');
});
//返回一个普通值
promise.then(res => {  
    console.log(res); //success
  return "hello world";
}, err => {
    console.log(err); 
}).then(res => {
    console.log(res); //hello world
}, err => {
    console.log(err); 
})
b.在then方法的回调里返回一个新的Promise
let promise = new Promise((resolve, reject) => {
    resolve();
});
//返回一个新的Promise
promise.then((res)=>{  
    return new Promise((resolve,reject)=>{ 
        resolve('hello world'); 
    })
},(err)=>{
    console.log(err); 
}).then((res)=>{
    console.log(res); //hello world    
},(err)=>{
    console.log(err);
})

4.promise的其他方法的实现

(1)promise.catch 方法

相当于调用 then 方法, 但只传入 Rejected 状态的回调函数

catch (onRejected) {
    return this.then(undefined, onRejected);
}
(2)promise.reject 方法
static resolve (value) {
  return new Promise(resolve => reject(value));
}
(3)promise.resolve方法
static resolve (value) {
  return new Promise(resolve => resolve(value));
}
(4)promise.all 方法
  • 【经典的手写promise.all 的面试题】

  • 返回一个Promise对象

  • 遍历传入的list集合; Object.entries方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历属性的键值对数组

  • 数组参数如果不是Promise实例,先调用Promise.resolve

  • 所有状态都变成 fulfilled 时返回的Promise状态就变成 fulfilled

  • 有一个rejected时返回的Promise 状态就变成 rejected

static all(list){
    return new Promise((resolve, reject) => {
    let values = [];
    let count = 0;
   for (let [i, p] of list.entries()) {
      this.resolve(p).then(res => {
        values[i] = res;
        count++;
        // 所有状态都变成fulfilled时,就resolve
        if (count === list.length) {
            resolve(values);
        }
      }, err => {
        // 有一个被rejected时,就rejected
        reject(err);
      })
    }
  })
}

(5)promise.race 方法
  • 只要有一个实例率先改变状态,新的Promise的状态就跟着改变
static race (list) {
  return new MyPromise((resolve, reject) => {
    for (let p of list) {
     //有一个状态改变时,就rejected或resolve
      this.resolve(p).then(res => {
        resolve(res)
      }, err => {
        reject(err)
      })
    }
  })
}
(6)promise.finally 方法
  • finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
finally (cb) {
  return this.then(
    value  => Promise.resolve(cb()).then(() => value),
    reason => Promise.resolve(cb()).then(() => { throw reason })
  );
};

5.promise其他方法的使用

(1)Promise.all( )
  • 传入的参数是一个数组

  • 若全部都是成功的回调,则执行会输出返回值组成的数组 【传入p1,p2成功的回调】

//成功
let p1 = new Promise((resolve,reject) => {
  let value = {
        code:0,
        msg:'one success'
    };
  resolve(value);
});
//成功
let p2 = new Promise((resolve,reject) => {
  let value = {
        code:1,
      msg:'two success'
  };
  resolve(value);
});
//失败
let p3 = new Promise((resolve,reject) => {
  let reason = 'all error';
  reject(reason);
});

Promise.all([p1,p2]).then((val) => {
  console.log(val);
}).catch(err=>{
  console.log(err);
});

输出结果:

输出结果
  • 若存在失败的回调,则直接返回失败的信息。【加入P3失败的回调】
Promise.all([p1,p2,p3]).then(val => {
  console.log(val);
}).catch(err=>{
  console.log(err);
});

输出结果:

输出结果

【适合场景】

如上p1,p2,p3的三个情况,只有全部是成功的回调promise才会成功状态,若有一条失败则promise返回状态。

  • 比如现在有一个表单页,里面有若干多个表单项,只有在所有表单都通过校验才允许用户进行下一步操作。
  • 每一个业务环节需要顺序执行,执行顺序不可变,状态不可逆,上一个执行结果可能需要作为下一步的输入。
(2)Promise.race( )
  • 传入的参数是一个数组

  • 会返回最先出结果的那个元素的状态

//成功回调,直接返回
let p1 = new Promise((resolve,reject) => {
  let value = {
    code:0,
    msg:'one success'
  };
  resolve(value);
});

//成功状态,延迟1秒返回
let p2 = new Promise((resolve,reject) => {
  let value = {
    code:1,
    msg:'two success'
  };
 setTimeout(()=>{
     resolve(value);
  },1000);
});

//失败状态,延迟0秒返回   
let p3 = new Promise((resolve,reject) => {
  let reason = 'race error'
  setTimeout(()=>{
     reject(reason);
  },0);
});

Promise.race([p1,p2,p3]).then(val => {
  console.log(val);
}).catch(err=>{
  console.log(err);
})

输出结果:
为什么不输出延迟0秒的p3 ‘race error’ 呢,因为这个牵扯到事件执行机制,同步任务先执行,异步setTimeout任务挂起,同步任务执行结束之后再执行异步。所以p3没有p1返回的快。

输出结果

【适用场景】

如上p1,p2,p3,不管他们的状态是成功还是失败,只是返回最先执行完的那个。所以promise.race可以用于快捷地测试接口反应速度。


参考:https://juejin.im/post/5afd2ff26fb9a07aaa11786c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,603评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,422评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,478评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,116评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,037评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,832评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,075评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,685评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,976评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,028评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,830评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,584评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,101评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,229评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,521评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,121评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,314评论 2 339