Promise(javaScript)

一、背景

JavaScript是单线程语言,所以在执行耗时较大的任务时会导致页面阻塞。为了解决页面阻塞,异步执行被提了出来。但是异步带来一个执行顺序的问题,通过回调函数的嵌套,可以实现异步任务的依次执行,但是嵌套的回调函数代码耦合性强,阅读起来不直观,一旦嵌套过多,就会产生所谓的回调地狱,会带来以下问题:

1. 代码臃肿
2. 可读性差
3. 代码复用性差
4. 耦合高,维护性差
5. 只能在回调中处理异常

doSomething(function(result) {
  doSomethingElse(result, function(newResult) {
    doThirdThing(newResult, function(finalResult) {
      console.log('Got the final result: ' + finalResult);
    }, failureCallback);
  }, failureCallback);
}, failureCallback);

二、什么是Promise

1. 概念

    Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

2. 特点

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

注意,为了行文方便,本章后面的resolved统一只指fulfilled状态,不包含rejected状态。

有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
 

三、 Promise的使用

1. 基本用法

   ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

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

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

resolve和reject这两个回调可以在生成Promise实例后,调用then方法定义

const promise = new Promise(function(resolve, reject) {
  // ... some code
 
  if (/* 异步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

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

Promise 新建后就会立即执行。

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

then()

then 方法就是把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。

而 Promise 的优势就在于这个链式调用。我们可以在 then 方法中继续写 Promise 对象并返回,然后继续调用 then 来进行回调操作。

可有两个参数,第一个是成功 resolve 调用的方法,第二个是失败 reject 调用的方法

let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});
 
promise.then(function() {
  console.log('resolved.');
});
 
console.log('Hi!');
// Promise
// Hi!
// resolved
/*
Promise 新建后立即执行,所以首先输出的是Promise。然后,then方法指定的回调函数,将在当前脚本所有同步任务执行完才会执行,所以resolved最后输出。
*/

 2. Promise 基本API 

(1)实例方法

  .then()

得到promise内部任务的执行结果。

.catch()

得到promise内部任务执行失败的结果。

.finally()

成功与否都会执行 在项目中 我们基于axios发送请求时(axios其实也是一个基于ajax用promise封装的http请求库),往往会开启loading,我们可以通过finally,不管请求成功与否,关闭loading,而不需要在成功和失败的时候写两次关闭loading代码。)

 <script type="text/javascript">
    /*
      Promise常用API-实例方法
    */
    // console.dir(Promise);
    function foo() {
      return new Promise(function(resolve, reject){
        setTimeout(function(){
          // resolve(123);
          reject('error');
        }, 100);
      })
    }
    // foo()
    //   .then(function(data){
    //     console.log(data)
    //   })
    //   .catch(function(data){
    //     console.log(data)
    //   })
    //   .finally(function(){
    //     console.log('finished')
    //   });
 
    // --------------------------
    // 两种写法是等效的
    foo()
      .then(function(data){
        # 得到异步任务正确的结果
        console.log(data)
      },function(data){
        # 获取异常信息
        console.log(data)
      })
      # 成功与否都会执行(不是正式标准) 
      .finally(function(){
        console.log('finished')
      });
  </script>


.all()

Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve转换为一个promise)。它的状态由这三个promise实例决定

小案例 

现在有这样的一个需求,执行按钮的触发条件是必须以上两个表单都验证成功,那么可以通过将每个表单的验证结果包装成promise对象,通过Promise.all 实现


 
function promise1() {
            return new Promise(function (resolve, reject) {
               this.$refs.form1.validate(v => {
                   if(v) resolve()
               })
 
            })
        }
 
        function promise2() {
            return new Promise(function (resolve, reject) {
               this.$refs.form2.validate(v => {
                   if(v) resolve()
               })
 
            })
        }
 
        Promise.all([promise1(), promise2()]).then(resolve => console.log('按钮触发事件')
        ).catch((e) => console.log(e))


.race()

Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilled或rejected),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数。

<script type="text/javascript">
    /*
      Promise常用API-对象方法
    */
    // console.dir(Promise)
    function queryData(url) {
      return new Promise(function(resolve, reject){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
          if(xhr.readyState != 4) return;
          if(xhr.readyState == 4 && xhr.status == 200) {
            // 处理正常的情况
            resolve(xhr.responseText);
          }else{
            // 处理异常情况
            reject('服务器错误');
          }
        };
        xhr.open('get', url);
        xhr.send(null);
      });
    }
 
    var p1 = queryData('http://localhost:3000/a1');
    var p2 = queryData('http://localhost:3000/a2');
    var p3 = queryData('http://localhost:3000/a3');
     Promise.all([p1,p2,p3]).then(function(result){
       //   all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
       console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
     })
    Promise.race([p1,p2,p3]).then(function(result){
      // 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
      console.log(result) // "HELLO TOM"
    })
  </script>

 (2)promise 的状态改变

Promise在执行时,其状态也会发生相应的改变。

peding: Promise执行时,其内部任务还未执行,.then还未拿到执行结果。(初始化时,执行器函数执行(同步执行),如果同步的直接改变状态,则可以看成一个段同步代码)

resolved:内部任务执行完毕,状态为成功。(resolved被调用时)

rejected:内部任务执行完毕,状态为失败。(rejected被调用时)

   a. pending 变为 resolved

   b. pending 变为 rejected

说明: 只有这 2 种, 且一个 promise 对象只能改变一次 无论变为成功还是失败, 都会有一个结果数据 成功的结果数据一般称为 vlaue, 失败的结果数据一般称为reason。

(3)promise的基本流程 


(4)promise的执行顺序

当我们new一个Promise时,传入的回调函数为同步代码 ,会立即执行,而.then() .catch()里的为异步微任务。

console.log(1)
setTimeout(function(){
  console.log(2)
}, 0)
new Promise(function(resolve){ //这里的回调是同步的
  console.log(3)
  resolve()
}).then(function(){  //异步微任务
  console.log(4)
})
console.log(5)
 
 
//输出结果 1,3,5,4,2


四、 Promise的三个缺点

1.无法取消Promise,一旦新建它就会立即执行,无法中途取消

2.如果不设置回调函数,Promise内部抛出的错误,不会反映到外部

3.当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成

注意:如果promise不通过.then或.catch方法捕获错误,在promise执行为reject状态时,会抛出一个reject错误,导致console面板报红,后面代码不在执行 ,我们也可通过配合async await 及try catch方法捕获错误执行相关逻辑操作。

 try {
        const res = await getArticleById(this.articleId) // getArticleById()是封装了的axios的api请求方法 本质也为一个promise
        this.articleInfo = res.data
        this.isLoading = false
        this.previewImage()
      } catch (err) {
        this.isLoading = false
        if (err.response && err.response.status === 404) {
          this.is404 = true
        }
      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值