关于promise的一些总结

作为前端开发人员,对于promise应该都是不陌生,基本上都有过,new promise, 然后.then, 原型里也提供了一些方案,race, all等等。。。对于解决同步的问题可谓是十分方便。然而,前段时间面试:
“知道promise吗”,
“知道”,
“那你说说什么是promise”,
“promise就是解决异步操作的一种方案,避免的函数回调”,
“嗯,那还有呢,具体怎么实现的呢”
“emmmm…”
“ok,那你知道async么,这个和promise区别是什么呢?”
…真的是一时语塞,当时紧张的随便说了两句,现在都忘了自己说了些什么。原来平时真的是专注于怎么方便怎么来,本着会用就行了的心态果然很容易把自己的路走窄呀。

所以,还是乖乖关注其方法本身吧。

#####为什么要用promise呢?
答案很明显,promise是为了解决异步的可操控性。node.js是单线程异步执行环境的。如果两异步接口存在依赖关系,比如A接口需要依赖B接口,那么我们希望A接口在B接口之前执行。
我们也许用如下的方式去控制方法的执行顺序
B接口里需要执行ajax请求,在B方法中定义一个变量val来监听ajax请求是否返回正确的值,再通过val来判断是否需要调用A方法。(以下代码为简写,理顺思路就好)

   function A(){
      console.log('----输出A----')
   }
   function B(val){
    if (val < 5) {
      A();
    }
   }
   B(4);  

或者是通过setTimeOut来控制方法的执顺序。

   function A(){
      console.log('----输出A----')
   }
   function B(val){
      console.log('---val----'+val);
      setTimeout(()=>{
         A();
      },5000)
   }
   B(4);

通过函数回调的方式如果是非常简单的操作,这样的写法也未尝不可。
但是如果是很复杂的逻辑,也许过了十天自己都回忆不起来,这是个啥。。
另外,这样写的坏处,就真的是造成了代码冗余,而且不能复用,并且让你觉得写代码是件繁琐的事情。
函数回调,很可能遇见信任问题,what?
1.调用回调过早(在它开始追踪之前)
2.调用回调过晚(或者不调)
3.调用回调太少或者太多次
4.吞掉了可能发生的错误/异常
5.没有传递正确的参数(还是有可能的,例如接口相应超时,超出了等待时间)

总之使用回调的方式,确实会出现很多让人难受又很奇葩的错误。

#####首先会用。ok,会用的程度是什么样子的呢?

  new Promise(function(resolve, reject){
    console.log('---执行步骤一,输出,成功---')
    resolve();
  }).then(function(resolve,reject){
    console.log('---执行步骤二,输出,失败----')
    reject();
  }).catch(function(resolve, reject){
    console.log('----最后一步失败---')
  })

输出
—执行步骤一,输出,成功—
—执行步骤二,输出,失败----
----最后一步失败—

甚至,我们在vue/react中,使用axios,fetch,dispatch等请求数据也会去支持promise API,非常直观的表达了函数的调用顺序。比如说:

  dispatch({
    type: 'xxx/xx',
    params:{
      content: 'test1'
    }
  }).then(res => {
    if (res) {
      ...doSomeThing...
    }
  })

以上,我想大部分都用到这个程度吧。

#####来剖析剖析promise

如果你来实现一个promise函数呢?应该怎么写?我们沿着这个思路,写一个promise的实现方案,顺便来了解了解它的思想。
我们以生活中的例子:
比如有一天我们需要外出办一件很紧急的事情,但是朋友又要来家里聚餐。最理想的状态就是,当我办完紧急的事情回来后,可以和朋友们聚在一起,吃着美食。那么promise在这时候充当的角色就是我的小助手mm,她需要为我做好一顿美食。接下来,我需要列好任务,告诉她如何才能做好这顿美食。以下就是我的任务清单。
1.去菜市场买菜
2.将买回来的菜熬成汤,做成好吃的东西
3.打电话告诉我的朋友通知他们过来聚餐
4.通知我回家

1,2,3步骤我们就理解为异步的执行过程吧,但是我要告诉mm我希望的过程。那么将上面的话用promise的写法,就可以翻译成

    // 告诉mm帮我做几件连贯的事情,先去菜市场买菜
    new Promise(买菜)
    //用买好的菜做饭
    .then((买好的菜)=>{
        return new Promise(做饭);
    })
    //打电话告诉朋友们饭好了
    .then((做好的饭)=>{
        return new Promise(饭好了);
    })
    //通知完朋友打电话通知我
    .then((通知朋友玩了)=>{
        电话通知我();
    })

promise是承若我一定帮你做一件事情,并且会告诉你事情的结果。

那么promise的核心还在于它的状态机制,一共有三个状态。

pending: 异步正在进行中
resolved: 执行成功
reject: 执行失败

那么按照状态,则是pendding => resolved => 返回结果
=> reject => 返回错误结果
也就是说,执行成功后,pedding状态变为resolved,执行失败后,pedding状态变为reject。这个状态一旦发生改变了,就不会再变回去了。

#####那就开始实现吧

这里就简单说说思路,具体时间这里整理时间也没有那么多了。

promise里面有then,resolve,reject这两个静态方法,也提供了all,race这些方法。

开始的第一步:
初始化promise实例,需要达到的目的:
1.改变promise中的status状态,将pedding状态变为resolved或者rejected。
2.当检测到是成功还是失败的状态时,返回相应的执行函数,传递出promise执行对象

  (export default) class PromistMine{
    constructor(executor){
      // 状态,promise里面基本上就这种状态了
      this.status = 'pedding';
      this.value = undefined; // 返回失败的值
      this.reason = undefined; // 失败成功原因
      this.onRejectCallBacks = []; // 存放失败的回调,这个是成功的回调函数
      this.onResolveCallBacks = []; // 存放成功的回调,这个是失败的回调函数

      /**
       * 处理的两种状态
       **/

      // 执行成功的状态
      let resolve = (reason) => {
        if (this.status === 'pedding') {
          this.status = 'resolved';
          // 把成功后要返回的信息透传
          this.reason = reason;
          this.onRejectCallBacks(fn => fn());
        }
      }
      
      // 执行失败的状态
      let reject = (data) => {
        if (this.status === 'pedding') {
          this.status = 'rejected';
          this.value = data;
          this.onRejectCallBacks(fn => fn())
        }
      }

      // 在new Promise时,都需要传递两个参数,resolve,reject用于处理方法是否成功后的回调
      try {
        executor(resolve,reject);
      } catch (e) {
        reject(e) // 执行执行器,如果throw new Error('error') 就直接走reject了
      }
    }
  }

开始的第二步:
实现resolve和reject这两个静态方法。
resolve和reject这两个方法本质上其实是一样的。这里是promise提供的两个静态方法,目的在于,状态是成功或者失败时,返回自身。
这两个静态方法其实和构造函数里的resolve与reject原理一样。

开始的第三步:
实现then的链式调用。
在平时使用promise,then字面意思是“下一步,接下来”。这个回调函数里,可以做的事情是:1.执行上一步的方法,并且在上一步的返回结果里执行一系列的操作。2.返回自身实例,记录执行结果,便于链式调用。

在原型上添加方法,new的时候直接继承了

  PromistMine.prototype.then((onFulfilled, onRejected) => {
    // 因为从原型上新增的方法,所以可以拿到this作用域
    if (this.status === 'pendding') {
      // 将报错或者成功的信息存起来
      this.onRejectCallBacks = this.onRejectCallBacks.push(onRejected);
      this.onResolveCallBacks = this.onResolveCallBacks.push(onFulfilled)
    } else if (this.status === FULFILLED){
      onFulfilled(value);
    } else {
      onRejected(this.reason);
    }
    return this; // 返回其本身
  })

大概的思路就是这些了。能看到这已经很不容易了。

记录一下常用race,all的使用

先说说all吧。

promise的all方法,在官方的类型是这样去描述这个方法的。它允许多个promise对象以数组的形式传入。

 /**
     * Creates a Promise that is resolved with an array of results when all of the provided Promises
     * resolve, or rejected when any Promise is rejected.
     * @param values An array of Promises.
     * @returns A new Promise.
     */
    all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;

promise提供的all方法,有一种共存亡的概念。当数组内所有的promise都被接受了,那么执行成功的回调;如果传入的promise数组中有一个被拒绝了,那么就执行失败后的回调;如果存在多个promise被拒绝了,返回的信息将是第一个promise被拒绝的消息。

for example,其他的状况就不一一列举了:

  const demo1 = new Promise((resolve, reject)=>{
    reject('接受到')
  });
  const demo2 = new Promise((resolve,reject)=>{
      setTimeout(()=>{
          reject('接收到了')
      },122)
  })
  Promise.all([demo1,demo2]).then(resolve=>{
      console.log(resolve)
  }, reject=>{
      console.log(reject)
  })
  output: 接受到

promise提供的race方法,race按表面的意思可以知道是赛跑的意思。race的入参和all方法是一样。允许传入多个promise对象。但是race更追求的是速度,不管是接受状态还是拒绝状态,谁快谁先输出。

var promise1 = new Promise(function(resolve, reject) {
    setTimeout(resolve, 110, 'one');
});

var promise2 = new Promise(function(resolve, reject) {
    setTimeout(reject, 100, 'two');
});

Promise.race([promise1, promise2]).then(function(value) {
  console.log(value);
  // Both resolve, but promise2 is faster
});

在前面提到,在promise中,有三种状态,pending,resolved,rejected。但在pedding变为resolved或者rejected时,这个状态我们叫决议状态。

在静态方法reject,resolve这两个方法,可以改变返回的状态,reject,resolve不会直接传参,而是将参数当做拒绝或者接受原因拒绝promise。

在resolve方法中,传入一个reject,此时返回的也是一个reject,被拒绝的promise。

例如

new Promise((resolve, reject)=>{
  resolve(Promise.reject('---解决了---'));
}).then((resolve)=>{
  console.log('----成功解决---');
  console.log(resolve)
},(reject)=>{
  console.log('-----失败解决---');
  console.log(reject);
})
> -----失败解决---
> ---解决了---

记录一下promise的then
promise提供了then的方法,方便函数的回调。而前面我们也提到,then它自身的意义在于记录执行结果,返回其本身,这样就可实现链式调用了。
promise在平时用到最多的场景,调用接口后再执行其他的操作。在我刚刚接触promise时,总会写出让人惊奇的代码,找错误找了半天,最主要的就是平时对promise的理解还不够。
让人惊奇代码之一:

  new Promise((resolve,reject)=>{
    console.log('---执行步骤1---');
  }).then(()=>{
    console.log('---执行步骤2---');
  }).then(()=>{
    console.log('---执行步骤3---');
  })
  ---执行步骤1---

这里的promise并没有执行resolve,所以promise一直在一个决议的状态,也没有返回其自身的实例,所以then里面的方法不执行那是当然的。

让人惊奇代码之二:

    const promise = new Promise((resolve, reject) => {
       resolve('完成');
    });
    promise.then((msg) => {
        console.log('first messaeg: ' + msg);
    }).then((msg) => {
        console.log('second messaeg: ' + msg);
    });
> first messaeg: 完成
> second messaeg: undefined

这里一直取不到值,又是为什么呢?
悄悄的改一下代码

    const promise = new Promise((resolve, reject) => {
       resolve('完成');
    });
    promise.then((msg) => {
        console.log('first messaeg: ' + msg);
        return 'new'+msg;
    }).then((msg) => {
        console.log('second messaeg: ' + msg);
    });
> first messaeg: 完成
> second messaeg: new完成

为什么呢?因为第二个then里,接受上一个return的返回值,也就是新的promise是根据上一个函数的返回来进行决议,但是上一个then并没有return值,默认值为undefined,所以第一段代码会return一个undefined。

/------------------------------------------一年后我又回来了-----------------------------------------------/
补录上前段时间遇见的场景:(循环下的promise)
请求A,其返回类型为[{id:1, name: 2},{id:2, name:3}]这样的list集合
请求B为后来加上的接口,其返回值为请求A下的每一项更详细的数据
需求:将请求B的数据加载到A的结果中返回。

咋一看,几个for循环不就搞定的事情嘛。其实不是。
框架的技术栈是react,在render阶段中,dom树会不断的进行新旧对比,而不断的去更新dom结构,如果在render中去调用接口,那么会发起很多不必要的请求。(可以在componentDidMount这个状态中去调取接口,但是两个接口是异步请求,且在for循环赋值时,因为事务造成代码执行顺序不一样而无法及时拿到更新后的值)

我要的结果,A先执行完再执行B,遍历A,将B的数据插入,再传递给dom。这样既不会调用多余的接口,进行一遍for循环就ok,我的写法如下:

 const promiseA = new Promise((resolve, reject) => {
      dispatch({
        type: 'xxx/getDomainDeployInfo',
        payload: this.params,
      }).then((res) => {
        if (res && res.resultObj) {
          resolve(res.resultObj);
        } else {
          reject();
        }
      })
    });
    promiseA.then((res) => {
      let promiseList = [];
      let promise = Promise.resolve();
      res.forEach((result) => {
        const {xxx, batchNumber} = result;
        if ( xxxx&& batchNumber == 1) {
          const params =  {
            xx1,
            xx2
          }
          promise = promise.then(() => {
            return new Promise((resolve) => {
              dispatch({
                type: 'xxxxx/fetchXX,
                payload: params,
              }).then((res1) => {
                if (res1.success && res1.resultObj){
                  const paramsOne = {
                   ...params,
                   ...res1.resultObj
                  };
                  promiseList.push(paramsOne);
                  resolve(paramsOne);
                } else {
                  resolve();
                }
              })
            })
          })
        }
      })
      promise.then((value)=>{
        let list = res;
        if (res && res.length > 0 && value ) {
          list = res.map((info) => {
            if (info.domain == value.domain) {
              const { c= {}, v= {}} = value;
              return {...info, c, v};
            } else {
              return info;
            }
          })
        }
        _this.setState({
          SSSS: list,
       })
      })
    })
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值