Ajax的Promise应用

异步回调的问题

  1. 之前处理异步是通过纯粹的回调函数的形式进行处理
  2. 很容易进入到回调地狱中,剥夺了函数return的能力
  3. 问题可以解决,但是难以读懂,维护困难
  4. 稍有不慎就会踏入 回调地狱—嵌套层次深,不好维护

在实际的使用中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最常见的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。

// 简单的ajax原生实现
var context;
var xhr = new XMLHttpRequest();
xhr.open('get', 'https://zsummer.com');
xhr.send();
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200) {
        context= xhr.response;
        console.log(context);
    }
}

在ajax的原生实现中,利用了onreadystatechange事件,当该事件触发并且符合一定条件时,才能拿到想要的数据,之后才能开始处理数据。

这样做看上去并没有什么麻烦,但如果这个时候,我们还需要另外一个ajax请求,这个新ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们就不得不等待上一个接口请求完成之后,再请求后一个接口。如下:

// 新请求
var url = 'https://zsummer.com?id=' + context.someParams;
var xhr1 = new XMLHttpRequest();
xhr1.open('get',url);
xhr1.send();
xhr1.onreadystatechange = function() {
   if (xhr1.readyState == 4 && xhr1.status == 200) {
       	console.log(xhr1.reponseText);
   }
}

这样可能没有什么,但是当出现第三个ajax(甚至更多)仍然依赖上一个请求时,我们的代码就变成了一场灾难。这场灾难,往往也被称为回调地狱

这个时候,我们可以用到Promise来使代码 更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来

Promise是什么?

  1. 主要用于异步计算
  2. 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果
  3. 可以在对象之间传递和操作Promise,帮助我们处理队列

总的来说,Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。

Promise 的原理

ES6 规定,Promise 对象是一个构造函数,用来生成 Promise 实例。通过在函数内部 return 一个 Promise 对象的实例,这样就可以使用 Promise 的属性和方法进行下一步操作了。

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

Promise 的缺点

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

Promise对象的特点

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

1)、Promise对象只有三种状态

  • 异步操作“未完成”(pending)

  • 异步操作“已完成”(resolved,又称fulfilled)

  • 异步操作“失败”(rejected)

这三种的状态的变化途径只有两种

  1. 异步操作从“未完成”到“已完成”

  2. 异步操作从“未完成”到“失败”

这种变化只能发生一次,一旦当前状态变为“已完成”或“失败”,就意味着不会再有新的状态变化了。因此,Promise对象的最终结果只有两种。

  1. 异步操作成功,Promise对象传回一个值,状态变为resolved
  2. 异步操作失败,Promise对象抛出一个错误,状态变为rejected
new Promise(function(resolve, reject) {
    if(true) { resolve() };
    if(false) { reject() };
})

Promise对象使用then方法添加回调函数。then方法可以接受两个回调函数,第一个是异步操作成功时(变为resolved状态)时的回调函数,第二个是异步操作失败(变为rejected)时的回调函数(可以省略)。一旦状态改变,就调用相应的回调函数。

2)、Promise对象中的then方法,可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行

function pro(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    }).then(function() {
        console.log('参数是number');
    }, function() {
        console.log('参数不是number');
    })
}
pro('hello world');
pro(111);

then方法的执行结果也会返回一个Promise对象。因此我们可以进行then的链式执行,这也是解决回调地狱的主要方式。

注意: then(null, function() {}) 就等同于catch(function() {})

了解了以上特点后,我们再来解决上面的回调地狱问题。

var url = 'https://zsummer.com';
// 封装一个get请求的方法
function getFun(url) {
    return new Promise(function(resolve, reject) {
        var xhr= new XMLHttpRequest();
        xhr.open('get', url);
        xhr.send();
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    try {
                        var response = JSON.parse(xhr.responseText);
                        resolve(response);
                    } catch (e) {
                        reject(e);
                    }
                } else {
                    reject(new Error(xhr.statusText));
                }
            }
        }
    })
}
// 链式编程  Promise的数据传递性
getFun(url).then(resp => console.log(resp));

这个时候我们来看,Promise的resolve和reject处理了很多可能出现的异常。resolve返回正确的结果,reject返回错误的结果。并且利用参数传递的方式,将正确结果或者错误信息通过他们的参数传递出来。这样,即使Ajax中的更多请求仍然依赖上一个请求时,仍然不会出现回调地狱的情况。

Promise的代码优化性

1)、Promise.all

当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。

Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,它才会去调用then方法。

var url = 'https://zsumer.com';
var url1 = 'https://yjz.com';
function getAll() {
	// 结合
    return Promise.all([getFun(url), getFun(url1)]);
}
// 返回
getAll().then(function(val) {
    console.log(val);
})

这样以后就可以用all并行执行多个异步操作,并且在一个回调中处理所有的返回数据,比如你需要提前准备好所有数据才渲染页面的时候就可以使用all,执行多个异步操作将所有的数据处理好,再去渲染。

2)、Promise.race

用法类似Promise.all,不过all是以跑的慢的为准执行回调,而Promise.race是以跑的快的为准执行回调。

function getRace() {
    return Promise.race([getFun(url), getFun(url1)]);
}
getRace().then(function(val) {
    console.log(val);
})

Promise.all是等所有的异步操作都执行完了再执行then方法,那么Promise.race方法就是相反的,谁先执行完成就先执行回调。先执行完的不管是进行了Promise.race的成功回调还是失败回调,其余的将不会再进入Promise.race的任何回调。Promise.race是竞争的意思。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值