ES6 中 Promise 详解

Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。

Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点:

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

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

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

简单来说,Promise 就是用同步的方式写异步的代码,用来解决回调问题,then 方法就是把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。而 Promise 的优势就在于这个链式调用。我们可以在 then 方法中继续写 Promise 对象并返回,然后继续调用 then 来进行回调操作。可有两个参数,第一个是成功 resolve 调用的方法,第二个是失败 reject 调用的方法

.catch()方法可以和 then 的第二个参数一样,用来指定 reject 的回调, 它的另一个作用是,当执行resolve的回调(也就是then中的第一个参数)时,如果抛出异常了(代码出错了),那么也不会报错卡死 js,而是会进到这个 catch 方法中。

Promise 的 all 方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。race 按字面解释,就是赛跑的意思。race 的用法与 all 一样,只不过 all 是等所有异步操作都执行完毕后才执行 then 回调。而 race 的话只要有一个异步操作执行完毕,就立刻执行 then 回调。
注意:其它没有执行完毕的异步操作仍然会继续执行,而不是停止

// 从 买笔 -> 写作业 -> 交作业 三个异步状态,都需要依赖上一步的结果才能执行
// 如果单纯在 ajax 异步回调里又做异步,就会形成回调地狱,看看 promise 是如何解决回调地狱的问题

// 买笔
function buy(){
  console.log("开始买笔");
  return new Promise( (resolve,reject) => {
    $.ajax({
      url: 'xxx.com',
      data: {name:'krry'},
      success: res => {
        console.log('买了笔芯');
        resolve(res); // 成功
      },
      error: (err) => {
        reject(err); // 失败
      }
    });
  });
}

// 写作业
function work(data){
  console.log("开始写作业:" + data);
  return new Promise( (resolve,reject) => {
    $.ajax({
      url: 'xxx.com',
      data: {name:'krry'},
      success: res => {
        console.log('写完搞定');
        resolve(res); // 成功
      },
      error: (err) => {
        reject(err); // 失败
      }
    });
  });
}

// 交作业
function out(data){
  console.log("开始上交:" + data);
  return new Promise( (resolve,reject) => {
    $.ajax({
      url: 'xxx.com',
      data: {name:'krry'},
      success: res => {
        console.log('上交完成');
        resolve(res); // 成功
      },
      error: (err) => {
        reject(err); // 失败
      }
    });
  });
}

// 调用异步的时候,如此简单,优雅地解决了回调地狱的问题
// 调用每一个方法的时候自动将参数放进去了
// 这里调用的是成功的回调
buy().then(work).then(out).then( data => {
  console.log(data);
});

// 上面的写法等同于这种写法:还是没有上面的简洁,推荐上面写法
buy().then(res => work(res)).then(res => out(res)).then( data => {
  console.log(data);
});

// 考虑失败的回调
buy().then( work, err => {
  console.log('买笔失败啦' + err);
}).then( out, err => {
  console.log('作业太难,写不了')
}).then( data => {
  console.log(data);
});

1. 注意:如果执行 buy() 异步失败,就会找 then() 有没有第二个参数或者有没有 catch() 方法
   有则进入这个的失败的回调(此时 promise 状态为 pending)
2. 当这个失败的回调执行成功后(没有发生 rejected 的情况下),就会继续找后面有没有 then(),
   没有则 promise 的状态就会置为 resolved 状态,有则继续是 pending 状态,继续执行 then() 第一个参数的回调函数
3. 如果继续执行的 then() 回调函数异步失败,且又找不到下一个 then() 的第二个参数或 catch() 方法,
   此时 promise 的状态置为 rejected
4. 跟着第一点,如果执行 buy() 异步失败,并且没有失败的回调,promise 的状态就只能 rejected,就不会再往下执行 then()

总结一点:成功,就继续执行 then() 第一个参数的回调,
         失败,就继续执行 then() 第二个参数或 catch() 的回调,
         依次这样执行,promise 的状态都是 pending

         一旦成功时找不到 then() 第一个参数的回调,promise 的状态就置为 resolved
         或
         一旦失败时找不到 then() 第二个参数或 catch() 的回调,promise 的状态就置为 rejected

         且 promise 状态一旦改变,就无法再次改变状态
//买作业本
function cutUp(){
  console.log('挑作业本');
  return new Promise((resolve, reject) => {
    $.ajax({
      url: 'xxx.com',
      data: {name:'krry'},
      success: res => {
        console.log('挑好购买作业本');
        resolve('新的作业本' + res);
      },
      error: (err) => {
        reject(err); // 失败
      }
    });
  });
  return p;
}

// 买笔
function boil(){
  console.log('挑笔芯');
  return new Promise((resolve, reject) => {
    $.ajax({
      url: 'xxx.com',
      data: {name:'krry'},
      success: res => {
        console.log('挑好购买笔芯');
        resolve('新的笔芯' + res);
      },
      error: (err) => {
        reject(err); // 失败
      }
    });
  });
  return p;
}

// 当 cutUp、boil 的状态都变成 resolved 时,promise 才会变成 resolved,并调用 then() 完成回调
// 但只要有一个变成 rejected 状态,promise 就会立刻变成 rejected 状态
Promise.all([cutUp(),boil()]).then(res => {
  console.log("写作业的工具都买好了");
  this.results = res;
  console.log(res); // 返回的是数组形式
});
// 使用解构赋值方式获取 all 返回的多个请求的数据
let [cutUpData, boilData] = this.results;
// 开发中使用 axios,封装了 promise
let [cutUpData, boilData] = await getAll();

Promise.race([cutUp(), boil()]).then((res) => {
  console.log("哈哈,我先买好啦");
  console.log(res);
});

race 使用场景很多。比如我们可以用 race 给某个异步请求设置超时时间,并且在超时后执行相应的操作。请求某个图片资源

function requestImg(){
            var p = new Promise(function(resolve, reject){
            var img = new Image();
            img.onload = function(){
               resolve(img);
            }
            img.src = 'xxxxxx';
            });
            return p;
        }
         
        //延时函数,用于给请求计时
        function timeout(){
            var p = new Promise(function(resolve, reject){
                setTimeout(function(){
                    reject('图片请求超时');
                }, 5000);
            });
            return p;
        }
         
        Promise.race([requestImg(), timeout()]).then(function(results){
            console.log(results);
        }).catch(function(reason){
            console.log(reason);
        });
        //上面代码 requestImg 函数异步请求一张图片,timeout 函数是一个延时 5 秒的异步操作。我们将它们一起放在 race 中赛跑。
        //如果 5 秒内图片请求成功那么便进入 then 方法,执行正常的流程。
        //如果 5 秒钟图片还未成功返回,那么则进入 catch,报“图片请求超时”的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值