一篇文章理解Promise原理

前提掌握知识:

微任务包括:

MutationObserver、Promise.then()或reject()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。

宏任务包括:

script、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。

javascript 中事件循环(event loop)机制:

  1. 将整个脚本作为一个宏任务进行执行。

  1. 一般来说按照从上到下的执行顺序,执行过程中解析到同步代码直接运行,解析到宏任务则放入宏任务队列,解析到微任务则放入到微任务队列。

  1. 当前宏任务执行完成后出队列,检查微任务队列,如果有微任务则依次执行完毕。

  1. 执行浏览器的UI线程进行渲染操作。

  1. 检查是否有Web Worker任务,如果有则执行。

  1. 执行完成本轮的宏任务后,回到第2步,依次循环执行,直到宏任务和微任务队列都为空为止。

发布订阅者模式:

通过一段简单的代码来实现一下 发布订阅者模式

let dep= {
    list:[],
    on:function(fn){
        list.push(fn)
    },
    emit:function(){
        this.list.forEach(event=>{
            typeof event==='function'?event() : null
        })
    }
}

以上代码实现了一个简单的发布订阅者模式,发布者存在一个数组list用于登记订阅者(异步执行函数),当某个时候执行了 emit,订阅的异步函数都会执行。例如订阅报纸的读者首先需要在报社登记自己的联系信息,当报社里面发版新的报纸时会通知在报社登记了信息的读者,然后他们就可以去报社或者收到新的报纸。

promise使用场景:

问:promise是在什么场景下被使用?

答:它为了解决 “函数的回调地狱

举个简单的例子:

firstCallback(value1=>{
    // 执行value1所在区域内的代码
    secondCallback(value2=>{
        // 执行value2所在区域内的代码
        thirdCallback(value3=>{
            // 执行value3所在区域内的代码
            ...
        })
    })
})

上面的这个例子中我们可以发现,每次增加了一个异步请求,那么就会多嵌套一层回调函数,过多的异步请求则会产生“回调地狱”这个问题,极其不易阅读和理解。

使用promise后重新改造代码:

firstCallback()
  .then(value1 => {
    // 执行value1所在区域内的代码
    return secondCallback()
  })
  .then(value2 =>{
    // 执行value2所在区域内的代码
    return thirdCallback()
  })
  .then((value3) => {
    // 执行value3所在区域内的代码
  })

由此可见,使用了promise改造后的代码清晰易懂,解决了“回调地狱”的问题。

promise核心:

promise的核心原理就是发布订阅者模式,创建两个队列分别保存回调函数的两个状态:成功(onResolve)失败(onReject)

promise状态:

  1. pending(正在等待):等待状态,比如正在进行网络请求xhr或者定时器setTimeout没有到时间。

  1. fulfilled(已成功):成功状态,promise 执行了 resolve时,处于该状态,并且调用 then()。then()中也可以接收失败回调。

  1. rejected(已失败):失败状态,promise 实行了 reject时,处于该状态,并且调用 catch()

Promise对象的状态改变只有两种:从pending变为 fulfilled 和 从pending变为rejected,一旦确定就不会再改变(不可逆)。

promise特点:

  1. new promise 时需要传递一个 executor 执行器,执行器会立即执行。

  1. 执行器中包含了两个参数:成功(onResolve) 的函数 和 失败(onReject)的函数,它们调用时可以接收任意值value。

  1. promise最初为pending(等待)状态,pending只能转为 fulfilled状态 或者 rejected状态,再执行相应队列中的任务。

  1. promise的then方法中可以传递两个参数,一个是成功的回调onfulfilled 和 失败的回调onrejected

  1. promise实例中如果状态是fulfilled 则执行then方法中的onfulfilled 回调函数,如果状态是rejected则执行then方法中的onrejected回调函数。

  1. promise 中同一个实例可以调用多次then方法,判断promise的状态,如果是pending状态,就需要将回调函数onfulfilledonrejected存储起来,等到promise的状态确认为fulfilled或者rejected才执行。

创建简单的promise:

promise 构造函数接收一个函数作为参数,函数中的两个参数分别为 resolve回调函数 和 reject回调函数。

let myPromise = newPromise((resolve,reject)=>{
    if(true){
        //如果状态为true 则promise 执行resolve,状态从 pending -> fulfilled.
        resolve(value)
    }else{
        //如果状态为false 则promise 执行reject,状态从 pending -> rejected.
        reject(error)
    }
})

resolve 函数的作用:将Promise实例对象的状态从"等待"转到"成功",并将异步操作的结果作为参数传入相应的成功回调函数执行。

reject 函数的作用:将Promise实例对象的状态从"等待"转到"失败",并将异步操作的结果作为参数传入相应的失败回调函数执行。

promise
  .then(value=> {
    //执行成功的回调函数
    console.log(value)
  })
  .catch(error=> {
  //执行失败的回调函数
    console.log(error)
  })

Promise链式写法:

//封装一个 自定义promise函数
functionmyPromise(flag,data,error){
    returnnewPromise((resolve, reject) => {
    if (flag) {
       //flag 为true 则执行 resolve()
      resolve(data)
    } else {
        // flag 为false 则执行 reject()
      reject(err)
    }
  });
}
//创建实例对象
const promise1 = newmyPromise(true,1,'失败了')
const promise2 = newmyPromise(true,2,'失败了')
const promise3 = newmyPromise(false,3,'失败了')
​
promise1
    .then((value)=>{
        console.log(value)
        return promise2
    },(error)=>{
    console.log(error)
    })
    //执行 promise2 的 回调
    .then((value)=>{
    console.log(value)
        return promise3
    },(error)=>{
    console.log(error)
    })
    //执行 promise3 的 回调
    .then((value)=>{
    console.log(value)
    },(error)=>{
    console.log(error)
    })
// 答:1 2 操作失败

Promise封装ajax:

// 使用promise 封装一个myAjax函数对象。
let myAjax = function(path,params,method){
    $.ajax({
        url:path,
        type:method,
        data:params,
        dataType:'json',
        success(res){
            resolve(res)
        },
        error(error){
            reject('出错了')
        }
    })
}
​
//执行 myAjax 函数
myAjax('xxx',{value:20},'get').then(res=>{
    console.log(res)
})

Promise相关方法(resolve、reject、all、race、allSettled):

1.Promise.resolve()

Promise.resolve方法的作用是可以把有需要的对象转为Promise对象。

resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;

只有promise调用then的时候,then里面的函数才会被推入微任务中。

Promise.resolve方法的参数分成四种情况:

1.参数是一个 Promise 实例

如果参数是Promise实例,则Promise.resolve()不会进行任何处理,直接返回这个实例。

2.参数是一个thenable对象

thenable对象指的是一个具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

let thenable= {
  then: function(resolve, reject) {
    resolve(42);
  }
};
​
let p1 = Promise.resolve(thenable);
// thenable对象的then方法执行后,对象p1的状态从pending变为fulfilled
p1.then((value)=> {
  console.log(value);  // 42
});

3.参数不具有then方法的对象

如果参数是一个原始值或者是一个不具有then方法的对象,执行Promise.resolve会返回一个新的Promise对象,状态转为fulfilled

const p = Promise.resolve('HelloWorld');
// HelloWorld 字符串被转换成 Promise对象,状态改变为 fulfilled。
p.then((s)=>{
  console.log(s)
});
// 答:HelloWorld

4.不带有任何参数

Promise.resolve方法调用时如果不传递任何参数,则直接返回一个fulfilled状态的 Promise对象。

setTimeout(function () {
  console.log('1');
}, 0);
​
Promise.resolve().then(function () {
  console.log('2');
});
​
console.log('3');
// 答:3 2 1
Promise.resolve().then(() =>console.log(2)).then(() =>console.log(3));
console.log(1); 
//答: 1, 2, 3

2.Promise.reject()

Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) =>reject('出错了'))
// 生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行
p.then(null, function (s) {
  console.log(s)
});
// 出错了

Promise.reject()方法的参数,会原封不动地传给onRejected回调函数作为参数。

3.Promise.all()

Promise.all用于将多个Promise实例包装成一个新的Promise实例,接收一个数组作为参数,也可以不是数组但是必须具备 iterator 接口。

const myAllPromise = Promise.all([myPromise1, myPromise2, myPromise3])

分析:myAllPromise 的状态 由myPromise1、myPromise2、myPromise3来决定,有以下两种情况:

  • 情况一:myPromise1、myPromise2、myPromise3的状态都为fulfilled,则myAllPromise 的状态变为fulfilled,此时myPromise1、myPromise2、myPromise3的返回值组成一个数组,传递给myAllPromise 的回调函数。

  • 情况二:myPromise1、myPromise2、myPromise3的状态只要有一个为 rejected,则myAllPromise 的状态变为rejected,三个实例中第一个返回rejected的实例的返回值会传递给myAllPromise 的回调函数。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result=>result)
.catch(e=>e);
​
const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result=>result)
.catch(e=>e);
​
Promise.all([p1, p2])
.then(result=>console.log(result))
.catch(e=>console.log(e));
//答: ["hello", Error: 报错了]

解析:上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,p2执行完catch方法后,也会变成fulfilled,导致Promise.all()方法参数里面的两个实例都会fulfilled,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

换一种变法:

const p1 = new Promise((resolve, reject) => {
        resolve("hello");
      })
        .then((result) =>result)
        .catch((e) =>e);
​
      const p2 =new Promise((resolve, reject) => {
        throw new Error("报错了");
      }).then((result) =>result);
​
      Promise.all([p1, p2])
        .then((result) =>console.log(result))
        .catch((e) =>console.log(e));
// 答:Error: 报错了

这里取消掉p2中的 catch方法,则p2就是一个rejected状态。promise.all就会接收到这个错误的状态。

4.Promise.race()

Promise.race和Promise.all 比较类似,同样都是将多个Promise实例包装成一个新的Promise实例,不同的是,Promise.race会监测包含的多个实例中第一个状态发生改变,则会将第一个发生改变的promise对象的返回值传递给新的Promise回调函数。

const p = Promise.race([
    fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() =>reject(new Error('request timeout')), 5000)
  })
]);
​
p.then(console.log).catch(console.error);
// 答:5s后输出 Error: request timeout

解析:如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数

5.Promise.allSettled()

Promise.allSettled方法也是接收一组Promise实例作为参数,包装成一个新的Promise实例。等到所有的参数实例都返回结果,不论是fulfilled还是rejected,才会执行包装实例Promise的回调函数。

const myPromise = {}
      myPromise.newAllSettled=function (promises) {
        return new Promise((resolve) => {
          let list= [];
          let len = promises.length;
          let count = len;
          for (let i=0; i<len; i++) {
            promises[i]
              .then(
                (res) => {
                  list[i] = {
                    status: "fulfilled",
                    value: res,
                  };
                },
                (error) => {
                  list[i] = {
                    status: "rejected",
                    reason: error,
                  };
                }
              )
              .finally(() => {
                // 循环完成后再resolve,防止出现空属性
                if (--count===0) resolve(list);
              });
          }
        });
      };
​
      myPromise
        .newAllSettled([p1, p2, p3])
        .then((res) =>console.log("Promise.allSettled:", res));

执行结果:

手写一个Promise:

function MyPromise(fn) {
        const that=this; //对Mypromise中的this状态进行备份
        this.status="pending"; //定义初始状态
        this.value=""; //初始值为空
​
        // 定义成功回调函数
        function resolve(value) {
          if (that.status==="pending") {
            that.status="fulfilled"; //改变状态pending->fulfilled
            that.value=value;
          }
        }
        // 定义失败回调函数
        function reject(value) {
          if (that.status==="pending") {
            that.status="rejected"; //改变状态pending->rejected
            that.value=value;
          }
        }
        // 这里的fn是执行器的意思包含resolve和reject两个回调函数
        fn(resolve, reject);
      }
​
      // 在Promise的原型上定义 then 方法
      MyPromise.prototype.then=function (onResolve, onReject) {
        const that=this;
        if (that.status==="fulfilled") {
          onResolve(that.value);
        }
        if (that.status==="rejected") {
          onReject(that.value);
        }
      };
​
      // 创建一个实例
      new MyPromise((resolve, reject) => {
        resolve("成功了");
      }).then(
        (res) => {
          console.log(res);
        },
        (err) => {
          console.log(err);
        }
      );
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.怪兽

希望大家能够多多支持,我会继续

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值