为什么那么多人手撕Promise?因为再不学会真的跟不上步伐了!

327 阅读6分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

Promise很多人都听说过,但是大家真的了解Promise么?为什么要用它,为什么要有它?以及它的出现为我们解决了怎么样的问题,这些都是我们需要知道的,接下来我们一步步进行分析,由浅入深。

为什么会出现Promise?

在我们JavaScript大环境下,我们的编程方式更多是基于异步编程,在异步编程中使用的最多的就是回调函数,先了解一下什么是回调函数。

回调函数指的是:被调用者回头调用调用者的函数,这种由调用方自己提供的函数叫回调函数。

假设场景:
获取学生成绩,并划分等级。
提供一些数据接口,接口数据嵌套,下一个接口数据依赖于上一个接口的返回值
学校 ===>班级 ===> 学生 ===> 成绩 ===> 等级的划分
//学校信息接口:http://xxxx.school.com/info?id='00001';   
返回值:[
	{className:'三年级2班',classId:'c00001'},
    {}...
]
//班级信息接口:http://xxxx.class_info.com/info?classId = classId

$.ajax({
    url:'',
    data:{},
    success:function(res){
        $.ajax({
            url:'',
            data:{},
            success:function(res){
					$.ajax({
                        url:'',
                        data:{},
                        success:function(res){

                        }
                    });
            }
        });
    }
});

以上的伪代码,回调函数层层嵌套,当上一次请求成功以后,才能进行下一次的数据请求,过程不受控制。
而且层层嵌套的过程不好理解,代码的易读性不高,这种情况我们称为‘回调地狱’

回调地狱:回调地狱最主要的就是因为功能逻辑代码嵌套的层次太多,导致可读性降低,维护困难,对代码性能,以及易读性,不友好。
案例:jq_ajax请求的回调嵌套

Promise的出现一个很重的的作用就是解决回调地狱这个问题的

什么是Promise

一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让您能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。

一个 Promise 必然处于以下几种状态之一:

  • 待定(pending) : 初始状态,既没有被兑现,也没有被拒绝。
  • 已兑现(fulfilled) : 意味着操作成功完成。
  • 已拒绝(rejected) : 意味着操作失败。

待定状态的 Promise 对象要么会通过一个值被兑现(fulfilled) ,要么会通过一个原因(错误)被拒绝(rejected) 。当这些情况之一发生时,我们用 promise 的 then 方法排列起来的相关处理程序就会被调用。如果 promise 在一个相应的处理程序被绑定时就已经被兑现或被拒绝了,那么这个处理程序就会被调用,因此在完成异步操作和绑定处理方法之间不会存在竞争状态。

因为 Promise.prototype.then 和  Promise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。

image.png

Promise怎么用?

定义

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

promise 是一个构造函数,需要实例化对象
promise构造函数,接受一个函数作为参数
作为参数的函数也接受两个参数,分别是:
-- resolve 
-- reject
-- 它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
​
--resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
(即从 pending 变为 resolved),
在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
​
--reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”
(即从 pending 变为 rejected),
在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
​
即  异步操作成功调用resolve函数  失败调用reject函数  
let mypromise = new Promise(function(resolve,reject){
// resolve();
// reject();
if(true){
    let obj = {username:'张好人',age:22};
    resolve(obj);//把异步操作的结果以参数的形式传出去,所以把obj作为参数传进resolve()中 
}else{
    reject('error');
}
});
 resolve ,reject  抛出数据以后,使用then() 来接收 (promise实例对象下有个then())
//promise下的then来接收resolve 或者reject 
    // 因为resovle 和reject把结果作为参数传递出来 
    // 所以下面res形参代表的就是处理结果
promise.then(function fn1(res){
   console.log(res);
});

判断输出结果:

<script>
    let promise = new Promise(function(resolve,reject){
        resolve(0);
        reject(1);
​
        return false;
​
    })
​
    // promise.then(function fn(res){
    //     console.log(res);
    // });
    promise.then((res)=>{
        console.log(res);
    });
</script>

demo_two

// Promise 新建后就会立即执行
 let promise = new Promise(function (resolve, reject) {
   console.log('Promise');  //1
   resolve();
 });
​
 promise.then(function () {
   console.log('resolved.'); //3
 });
​
console.log('Hi!');  //2

Promise.prototype.then

它的作用是为 Promise 实例添加状态改变时的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

<script>
    let p = new Promise((resolve, reject) => {
        if (true) {
            resolve('000');
        } else {
            reject('1111');
        }
    })
    
    let p1 = p.then((re) => {
        console.log(re);
        // return '哈嘿';
       
    }, (er) => {
        console.log(er);
    });
​
    p1.then((re)=>{
        console.log(re);
    },(err)=>{
        console.log(err);
    })
</script>

异步加载图片

<script>
    function loadImg(url){
        return new Promise(function(resolve,reject){
            let img = new Image();
            img.src = url;
​
            img.onload = function(){
                resolve(img);
            }
            
            img.onerror = function(error){
                reject(error);
            }
        })
    }
​
    let img_url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2Fedpic%2F4c%2Fa6%2F31%2F4ca631a8841304be2351295d50cf801d.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621953231&t=b5dde811f085ce8a929d6dbee958df85';
    loadImg(img_url).then(function(img){
        document.documentElement.appendChild(img);
    },function (error){
        throw error;
    });
</script>

Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

如果Promise 对象对象状态变为resolved,则会调用then()方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch()方法指定的回调函数,处理这个错误。另外,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。

let promise = new Promise(function(resolve,reject){
        if(false){
           resolve('ok');
        }else{
            reject('error');
        }
    })
​
    // 在调用then的时候,如果只写了resolve的情况,没有写reject
    // 则可以在then的resolve后面添写catch方法代替reject
    promise.then(function(res){
        console.log(res);
    }).catch(function(err){
        console.log(err);
    })

promise.all

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例

Promise.all([promise实例1,promises实例2])

//异步加载图片案例
#promise.all()
// Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例
// let proall = Promise.all([p1,p2,....]);
1)只有p1、p2、p3的状态都变成成功,proall的状态才会变成成功,此时p1、p2、p3的返回值组成一个数组,传递给proall的回调函数。
2)只要p1、p2、p3之中有一个被rejected,proall的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给proall的回调函数。

 // 加载图片
    function loadImg(imgUlr) {
        return new Promise(function (resolve, reject) {
            var img = new Image();
            img.src = imgUlr;
            img.onload = function () {
                resolve(img);
            }

            img.onerror = function () {
                reject('erro');
            }
        })
    }

    let img_url = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%253A%252F%252Fdingyue.ws.126.net%252F2021%252F0312%252Fb9eb24e5j00qpts36001kc000nq00xhm.jpg%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&refer=http%3A%2F%2Fnimg.ws.126.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621953737&t=409360e61738d61cade8122f8c7529cf';
    let img_url2 = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%253A%252F%252Fdingyue.ws.126.net%252F2021%252F0425%252F7dce84c9j00qs37ji001pc000hs013ic.jpg%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&refer=http%3A%2F%2Fnimg.ws.126.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621953737&t=805235acdeeb8feceb6f0170de344028';
    let img_url3 = 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fnimg.ws.126.net%2F%3Furl%3Dhttp%253A%252F%252Fdingyue.ws.126.net%252F2021%252F0316%252F778c9f86j00qq1qx6001pc000hs012jc.jpg%26thumbnail%3D650x2147483647%26quality%3D80%26type%3Djpg&refer=http%3A%2F%2Fnimg.ws.126.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621953737&t=de62b2d5cbbecdd5d1ffaad4c35c7c9f';
    //同时发送多次请求
    // Promise.all([请求1,请求2,。。。。]);
    // all:返回值,promise实例,resolved回调函数,接收返回数据时,也是以数组来接收的。
    Promise.all([
       loadImg(img_url), 
       loadImg(img_url2), 
       loadImg(img_url3), 
    ]).then((els)=>{
        //els 接收返回的img图片
        for(let i in els){
            document.documentElement.appendChild(els[i]);
        }
    }).catch((erro)=>{
        console.log(erro);
    })