原生JS -- 深度解读promise

1、概念

  • Promise作为一个单词,有承诺、许诺的意思。那我说其实作为一个程序它也是这个意思。
  • 此话怎讲?先让我们回到生活,比如我现在做出一个承诺,过几天我这篇文章的访问量会上1k。那么过几天,势必会出现两个结果之一:1、上1k了,我的承诺实现了;2、没有1k,我的承诺失败了。
  • 对于程序Promise,它也是如此,在你创建之时,它就许下了一个承诺,在将来它会出现两个结果之一,要么失败要么成功(就像几天后我博客的访问量,不可能上了1k又没上1k)。
  • 这就对应着Promise的三种状态,分别是pendding(承诺进行中),resolve(成功,也称为Fulfilled)和 Reject(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

真的有这么神奇吗?一个程序还能成精了不成!当然不是,下面我们就看看Promise的执行过程

2、语法

  • Promise是一个构造函数,需要new执行创建一个promise实例,并必须接收一个函数参数
  • 在回调函数中,再接收两个参数,第一个代表成功的参数,第二个代表失败的参数
    • 第一个参数接收的是实例的then方法的回调函数
    • 第二个参数接收的是实例的catch方法的回调函数
let p = new Promise(function(resolve, reject) {
	// 回调函数里面是正在进行时
    console.log("hello"); // hello
    setTimeout(() => {
        console.log("我是成功的延时器"); // 我是成功的延时器
        resolve(); // 如果这里的延时器触发时间更短,执行第一个参数(resolve)的函数,第二个参数(reject)的函数就不会执行
    }, Math.random() * 1000); // 延时器触发的时间随机
    setTimeout(() => {
        console.log("我是失败的延时器"); // 我是失败的延时器
        reject(); // 如果这里的延时器触发时间更短,执行第二个参数(reject)的函数,第一个参数(resolve)的函数就不会执行了
    }, Math.random() * 1000); // 延时器触发的时间随机
});
p.then(() => { // 实例的then方法在成功时执行
	console.log("成功了");
});
p.catch(() => { // 实例的catch方法在失败时执行
    console.log("失败了");
});

打印结果有两种:
原生JS -- 深度解读promise_第1张图片
在上述代码中,new Promise的回调函数里称为pendding,在这个状态下,所有的代码都会执行。如果第一个延时器触发的事件更短,就会执行resolve的函数,即由实例的then方法的回调函数传进来的代码,此时,reject的函数就不会执行(结果只能存在一个);反之亦然

  • 为了简洁代码,上述实例的api也可以写成以下语法
  • 由实例的then()接收两个函数参数,第一个参数表示成功,第二个参数表示失败
p.then(() => {
    console.log("成功了"); 
},() => {
    console.log("失败了"); 
});

3、应用

  • 下面以解决回调地狱的例子来解析Promise的应用

一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。

  • Promise解决回调地狱
let p1 = new Promise(function(resolve, reject) {
	// 假设使用ajax请求数据,它的回调函数接收的参数就是请求到的数据
    ajax(url1, function(res1) { 
        resolve(res1); // 请求成功,将数据传给外部的then方法
    })
})
let p2 = p1.then(function(res1) {
	// 在then方法的回调函数中在返回一个Promise的实例
    return new Promise(function(resolve, reject) {
        ajax(url2, function(res2) {
            resolve(res2);
        })
    })
})
p2.then(function(res2) {
    return new Promise(function(resolve, reject) {
        ajax(url3, function(res3) {
            resolve(res3);
        })
    })})
...
  • 通过在then方法的回调函数内部返回一个新实例,我们就可以不断在外面拿到一个新实例,不断套娃…,这样就可以使代码变得优雅可维护
  • 也正是由于内部返回了实例,所以上述代码还可以更简洁:
  • Promise的连缀写法
p1.then(()=>{
    return new Promise(()=>{}); // 整个then的方法都是p1的
}).then(()=>{
    return new Promise(()=>{}); // 整个then的方法都是p2的
}).then(()=>{
    return new Promise(()=>{}); // 整个then的方法都是p3的
})

4、批处理

4.1、Promise.all()方法

// 仍然假设ajax请求数据
let p1 = new Promise(function(resolve, reject) {
    ajax(url1, function(res1) {
        resolve(res1);
    })
})
let p2 = p1.then(function(res1) {
    return new Promise(function(resolve, reject) {
        ajax(url2, function(res2) {
            resolve(res2);
        })
    })
})
let p3 = p2.then(function(res2) {
    return new Promise(function(resolve, reject) {
        ajax(url3, function(res3) {
            resolve(res3);
        })
    })
})
let p = Promise.all([p1,p2,p3]); 
p.then((res)=>{ console.log(res); })
  • 如果上述三个请求都能成功拿到数据,打印的最终结果会是三个数据
  • 如果有一个请求失败了,最终一个数据都无法拿到

4.2、Promise.race()方法

// 仍然假设ajax请求数据
let p1 = new Promise(function(resolve, reject) {
    ajax(url1, function(res1) {
        resolve(res1);
    })
})
let p2 = p1.then(function(res1) {
    return new Promise(function(resolve, reject) {
        ajax(url2, function(res2) {
            resolve(res2);
        })
    })
})
let p3 = p2.then(function(res2) {
    return new Promise(function(resolve, reject) {
        ajax(url3, function(res3) {
            resolve(res3);
        })
    })
})
Promise.race([p1, p2, p3]).then((res) => { // 使用了连缀写法
	console.log(res); // 谁先执行完,就显示谁的数据
})
  • 不管有没有失败,都只拿到第一个执行结束的结果(比如,某个请求的路径错误,但是这个请求最先执行完,最终的结果只有一个报错:如404之类的)
  • 如果某个成功的请求最先执行完,最终的结果就是该请求拿到的数据
  • 最后总结下allrace区别(其实很语义化,理解起来不难)
    • race:不管有没有失败,只拿到第一个执行完的数据
    • all:只要有失败就拿不到数据,只有全部成功,才能拿到所有数据

你可能感兴趣的:(原生JS,js)