js promise的用法_年薪40w工程师的提示:学习Promise原理

fa0da65513294c6b8a447294c339c933.png

前言

我们知道JavaScript语言的执行环境是“单线程”,所谓单线程,就是一次只能够执行一个任务,如果有多个任务的话就要排队,前面一个任务完成后才可以继续下一个任务。

这种“单线程”的好处就是实现起来比较简单,容易操作;坏处就是容易造成阻塞,因为队列中如果有一个任务耗时比较长,那么后面的任务都无法快速执行,或导致页面卡在某个状态上,给用户的体验很差。

一直以来,JavaScript处理异步都是以callback的方式,在前端开发领域callback机制几乎深入人心。在设计API的时候,不管是浏览器厂商还是SDK开发商亦或是各种类库的作者,基本上都已经遵循着callback的套路。如果有很多ajax请求,callback的层级关系足够让人头晕,而且很大的增加了服务器的压力。近几年随着JavaScript开发模式的逐渐成熟,CommonJS规范顺势而生,其中就包括提出了Promise规范,Promise完全改变了js异步编程的写法,让异步编程变得十分的易于理解。

一.什么是Promise

Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一。简单点说,Promise是一种用于解决异步问题的思路、方案或者对象方式。Promise比传统的解决方案(回调和事件)更合理和更强大。它由社区最早提出和实现,ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。因为Promise规范已经出来好一段时间了,而且高版本的chrome、firefox浏览器都已经原生实现了Promise,只不过和现如今流行的类Promise类库相比少些API。

Promise简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说,Promise是一个对象,从它可以获取异步操作的消息。
Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。

可以把所谓Promise对象想象成是工厂生产线上的一个工人,一条生产线由若干个工人组成,每个工人分工明确,自己做完把产品传递给下一个工人继续他的工作,以此类推到最后就完成一个成品。这条生产线的组织机制就相当于Promise的机制,每个工人的工作相当于一个异步函数。后面会继续拿promise和这个例子进行类比。

Promise的好处

  • 用Promise 来处理异步回调使得代码层次清晰,
  • 便于理解,且更加容易维护

Promise的缺点

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

二.为什么用Promise

以ajax请求为例:

传统js方法:

getData(method, url, successFun, failFun){
  var xmlHttp = new XMLHttpRequest();
  xmlHttp.open(method, url);
  xmlHttp.send();
  xmlHttp.onload = function () {
    if (this.status == 200 ) {
      successFun(this.response);
    } else {
      failFun(this.statusText);
    }
  };
  xmlHttp.onerror = function () {
    failFun(this.statusText);
  };

使用Promise:

getData(method, url){
  var promise = new Promise(function(resolve, reject){
    var xmlHttp = new XMLHttpRequest();
    xmlHttp.open(method, url);
    xmlHttp.send();
    xmlHttp.onload = function () {
      if (this.status == 200 ) {
        resolve(this.response);
      } else {
        reject(this.statusText);
      }
    };
    xmlHttp.onerror = function () {
      reject(this.statusText);
    };
  })
  return promise;
}
 
getData('get','www.xxx.com').then(successFun, failFun)

很显然,这里把异步中使用回调函数的场景改为了.then()、.catch()等函数链式调用的方式。基于promise,可以把复杂的异步回调处理方式进行模块化。

大致Promise的结构可以分成如下:

Promise构造函数接受一个函数作为参数,函数里面有两个参数resolve和reject分别作为执行成功或者执行失败的函数
var promise=new Promsie(function(resolve,rejec){
    if(/*异步执行成功*/){
        resolve(value);
    }else{
        reject(error);
    }
})
通过then设置操作成功之后的操作,接受两个函数作为参数,第一个成功的操作必写,第二个失败后的操作可选
promise.then(function(){
    //回调执行成功之后的操作
},function(){
    //回调执行失败之后的操作,可选
});

三.Promise对象的组成

Promise对象仅有的三种状态:

pending:异步操作未完成。
resolved:异步操作已完成。
rejected:异步操作失败。

pending状态的Promise对象可能被填充了(fulfilled)值,也可能被某种理由(异常信息)拒绝(reject)了。当其中任一种情况出现时,Promise对象的then方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled和onrejected,它们都是Function类型。当值被填充时,调用then的onfulfilled方法,当Promise被拒绝时,调用then的onrejected方法, 所以在异步操作的完成和绑定处理方法之间不存在竞争)

因为Promise.prototype.then和 Promise.prototype.catch方法返回 promises对象自身, 所以它们可以被链式调用.

上面三种状态的变化只有两种模式,并且一旦状态改变,就不会再变:

异步操作从pending到resolved;
异步操作从pending到rejected;

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

38467a32bdb710acad10a9ecfcd3976c.png

图中有几个小细节:

图中的fulfill和reject只是表示指向不同结果,而不是指一个过程,在到达fulfilled,rejected状态前promise都处在pending状态。
settled包括了fulfilled和rejected。
promise只会在pending,fulfilled,rejected三种状态下切换。

四.promise基本用法

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

var promise = new Promise(function(resolve, reject) {
  // ... some code

  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

五.js手动实现promise

1.基本实现

一个Promise类的基本框架

function Promise(fn) {
  var resolveCallback = null
  var rejectCallback = null

  this.then = function(onResolved, onRejected) {
      resolveCallback = onResolved
      rejectCallback = onRejected
  }

  this.resolve = function(value) {
      this.resolveCallback(value)
  }

  this.reject = function(reason) {
      this.rejectCallback(reason)
  }

  fn(this.resolve, this.reject)
}

2.状态管理

上述的代码存在一个问题,resolve方法会调用多次,所以接下加入状态管理功能。

Promise内部存在3个状态:

  • pending
  • resolved
  • rejected

在现有代码基础上加入:

function MyPromise(fn) {
  let state = 'pending'
  var resolveCallback = null
  var rejectCallback = null
  var childResolve
  var childReject

  this.then = function(onResolved, onRejected) {
      resolveCallback = onResolved
      rejectCallback = onRejected
  }

  this.resolve = function(value) {
      if(state === 'pending') {
         this.resolveCallback(value)
         state = 'resolved' 
      } 
  }

  this.reject = function(reason) {
      if(state === 'pending') {
          this.rejectCallback(reason)
          state = 'rejected'
      }   
  }

  fn(this.resolve, this.reject)
}

3.链式调用

上述Promise实现可以完成正常的异步调用,但是却无法实现链式回调,原因在于其then方法没有返回一个新的Promise对象,所以接下来还需要改造then方法,实现链式调用:

  this.then = function(onResolved, onRejected) {
      if(state === 'pending') {
        resolveCallback = onResolved
        rejectCallback = onRejected
      }

      return new MyPromise((resolve, reject) => {
        ......
      })
  }

测试demo:

  var demo = new MyPromise((resolve, reject) => {
      setTimeout(() => {
          resolve('my first promise')
      }, 1000)
  })

  demo.then((msg) => {
      console.log(msg)
      return 'my second promise'
  }).then((msg) => {
      console.log(msg)
  })

其输出为:

my first promise

事实上,第二个promise对象的resolve reject方法从未被调用过,因而其onResolved onRejected的回调ye就无从调用。所以还必须指定时机调用字promise对象的resolve和reject。

所以首先需要在创建新promise对象时,记录其resolve和reject方法:

function MyPromise() {
    ......
    var childResolve
    var childReject

      this.then = function(onResolved, onRejected) {
      if(state === 'pending') {
        resolveCallback = onResolved
        rejectCallback = onRejected
      }

      return new MyPromise((resolve, reject) => {
        childResolve = resolve
        childReject = reject
      })
  }
}

接下来还需在resolve 和 reject方法中调用子对象的resolve和reject方法,整个Promise完整代码如下:

function MyPromise(fn) {
    let state = 'pending'
    var resolveCallback = null
    var rejectCallback = null
    var childResolve = null
    var childReject = null

    this.then = function(onResolved, onRejected) {
        if(state === 'pending') {
            resolveCallback = onResolved
            rejectCallback = onRejected
        }

        return new MyPromise((resolve, reject) => {
            childResolve = resolve
            childReject = reject
        })
    }

    this.resolve = function(value) {
        if(state === 'pending') {
            if(resolveCallback) {
                var ret = resolveCallback(value)
                childResolve(ret)
                state = 'resolved' 
            }

        } 
    }

    this.reject = function(reason) {
        if(state === 'pending') {
            if(rejectCallback) {
                var ret = rejectCallback(reason)
                childReject(ret)
                state = 'rejected'
            }
        }   
    }

    fn(this.resolve, this.reject)
  }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值