【JavaScript】回调地狱、Promise

文章目录

  • 1. 回调函数
  • 2. 异步任务
  • 3. 回调地狱
  • 4. Promise
    • 4.1 Promise定义
    • 4.2 Promise基础用法
      • 4.2.1 生成Promise实例
      • 4.2.2 Promise对象的执行顺序
      • 4.2.3 Promise的链式编程 then catch方法
      • 4.2.4 Promise.all()
      • 4.2.5 Promise.race()
    • 4.3 手写一个Promise
      • 4.3.1 Promise对于状态的控制
      • 4.3.2 Promise的后续处理(then catch)
      • 4.3.3 `Promise.all()`的实现
      • 4.3.4 `Promise.race()`的实现

1. 回调函数

当一个函数作为参数传入另一个参数中,并且它不会立即执行,只有满足一定条件后该函数才能执行。

回调函数放在另外一个函数(eg:parent)的参数列表中,作为参数传递给parent,然后在parent函数的某个位置执行。

eg:定时器,Ajax中存在回调函数


2. 异步任务

同步任务在主线程上排队,只有前一个任务执行完毕,才能执行下一个任务。

异步任务不进入主线程,而是进入异步队列,前一个任务是否执行完毕不影响下一个任务的执行。

不阻塞后面任务执行的任务就叫做异步任务。

在es6的Promise出来之前, 我们大多数时候是在使用回调函数(callback)事件来处理一些异步问题。

  1. 事件: 某个对象的属性是一个函数, 当发生一件事时, 运行该函数

  2. callback回调函数: 运行某个函数以实现某个功能的时候, 传入一个函数作为参数, 当某个特定的条件下的时候, 就会触发该函数

从本质上来说, 事件和回调函数也没有什么太大的区别, 仅仅就是函数放置的位置不太一样而已。

setTimeout(function() {
    // 这个函数就是callback回调函数
}, 10)


$ajax({
    url: '/api/getUserInfo',
    success: function() {
        // 成功的回调函数
    },
    error: function() {
        // 失败的回调函数
    }
})


var div = document.querySelector('#app');
div.addEventListener('click', function() {
    // 点击div以后执行该函数
}, false)
setTimeout(function() {
    console.log('执行了回调函数!');
},3000)

console.log('1111');
分析:
按照代码编写的顺序,
应该先输出“执行了回调函数”,
再输出“111”。

但实际执行结果为:	
1111
执行了回调函数!

3. 回调地狱

  • 回调地狱就是为是实现代码顺序执行而出现的一种操作
  • 回调函数中嵌套回调函数的情况就叫做回调地狱。

eg:我要说一句话,语序必须是下面这样的:”武林要以和为贵,要讲武德,不要搞窝里斗。”

setTimeout(function() {
    console.log('武林要以和为贵');
    setTimeout(function() {
        console.log('要讲武德');
        setTimeout(function() {
            console.log('不要搞窝里斗');
        },3000);
    },2000)
},1000)

在这里插入图片描述
回调地狱的问题:

  • 嵌套层次很深,可读性差,难以维护
  • 无法正常使用return和throw
  • 无法正常检索堆栈信息
  • 多个回调之间难以建立联系

4. Promise

4.1 Promise定义

  • Promise是异步编程的一种解决方案,比传统的解决方案(函数回调导致回调地狱、事件)更合理,更强大。
  • ES6中将Promise写进了语言标准,统一了用法,提供原生的Promise对象,获取异步操作的消息。Promise提供统一的API,各种异步操作可以用同样的方法来处理。
  • Promise是一个容器,容器中保存着某个未来才会结束的事件,通常是异步操作。

Promise对象的两个特点:

对象状态不受外界的影响。 Promise对象代表一个异步操作,有以下三个状态:

  • pending:挂起状态(或者称之为等待状态), 未决阶段的状态, 代表事情还在未决, 结果还没出来
  • fulfilled:已处理(或者称之为处理成功), 已决阶段的状态, 代表事情已经产生结果, 而且这个结果是一个可以按照预定逻辑走下去的结果
  • reject:已拒绝(或者称之为处理失败), 已决阶段的状态, 代表事情已经产生结果, 但是这个结果跟预想的不太一样, 通常为发生错误
    只有异步操作的结果,可以决定当前是哪一种状态,任何操作都无法改变这个状态。

拿一个网络请求来说, 请求的过程中为未决阶段: 状态为pedding, 而请求到了数据状态码为OK则是resolved状态, 而请求失败500服务器错误则是rejected状态

一旦状态改变,就不会再变任何时候都可以得到这个结果。 Promise对象的状态改变只有两种可能:

  • 从pending变为fulfilled
  • 从pending变为rejected
    只要这两种情况发生,状态就凝固了,不会再变,一直保持这个结果,这时九成宫为resolved(已定型)。

4.2 Promise基础用法

打印console.dir(Promise)
【JavaScript】回调地狱、Promise_第1张图片
说明Promise是一个构造函数,自己身上有all、reject、resolve这几个方法,原型上有then、catch等方法。


4.2.1 生成Promise实例

let promise = new Promise(function(resolve,reject){
    if() {  //异步操作成功
        resolve (value)
    }else {
        reject (error)
    }
})

Promise构造函数接受一个函数作为参数,这个函数的两个参数分别是resolvereject

  • resolve,reject是两个函数,是JavaScript引擎提供的,不用自己部署。
  • resolve函数的作用: 将Promise对象的状态从pending改为resolved,在异步函数操作成功的时候调用给,并将异步操作的结果作为参数传递出去。
  • reject函数的作用: 将Promise对象的状态从pending改为rejected,在异步操作失败的时候调用,并将异步操作报出的错误作为参数传递出去。
  • Promise实例生成之后,可以用then方法来分别指定resolved状态和rejected状态的回调函数
/* runAsync返回一个Promise实例,表示一段时间后才会发生的结果 */
function runAsync() {
    let p = new Promise((resolve,reject) => {
        setTimeout(function() {
            console.log('执行完成');
            resolve('随便什么数据')
        },2000);
    })
    return p;  
}


/* 过了指定ms指定的时间后,Promise实例的状态就会变为resolved */
runAsync();
// 打印结果:
// 执行完成

	
/*	过了指定ms指定的时间后
Promise实例的状态就会变为resolved,
然后触发then方法指定的回调函数

Promise实例生成之后
可以用then方法来分别指定resolved状态和rejected状态的回调函数
then方法回调函数中的参数是resolve和reject函数残敌出来的参数 */
runAsync().then(function(data){
    console.log(data);
})
// 打印结果:
// 执行完成
// 随便什么数据

4.2.2 Promise对象的执行顺序

let promise = new Promise((resolve, reject) => {
    console.log('Promise');
    resolve();
});
promise.then(() => {
    console.log('Resolved');
});
console.log('Hi');

在这里插入图片描述
分析:

  1. 实例化一个Promise对象,调用构造函数,首先执行console.log(‘Promise’);输出“Promise”,resolve()函数将Promise对象的状态从pending改为resolved
  2. 使用then方法指定resolved状态回调函数
  3. 输出“Hi”
  4. 当期脚本所有同步任务执行完成后,执行then方法指定的回调函数,输出“resolved"

4.2.3 Promise的链式编程 then catch方法

Promise对象的then方法用来接收处理成功时响应的数据,catch方法用来接收处理失败时相应的数据。

then:接收两个参数, 一个thenable,意为绑定Promise成功的回调 一个catchable,意为Promise失败的回调 注册的两个函数何时执行由Promise的状态决定, 一旦Promise的状态走向已决的resolved状态则thenable函数执行, 走向rejected状态则catchabale执行

const myPromise = new Promise((resolve, reject) => {

    // 我在这里直接触发resolve
    resolve('我是成功要传递的数据');
    
})

myPromise.then((data) => {
    console.log(data);
}, err => {
    console.log(err);
})

catch:接收一个参数, 意为绑定Promise任务失败的回调, 一旦整个Promise的实例走向了rejected, catchable一定会执行


const myPromise2 = new Promise((resolve, reject) => {
    reject('我是失败要传递的错误');  
})

// then方法的第二个参数可以不传
myPromise2.then(data => {
    console.log(data);
})

myPromise2.catch(err => {
    console.log(err);
})

无论是then方法还是catch方法结束一个都会返回一个新的Promise实例


4.2.4 Promise.all()

  • Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调

  • 用Promise.all来执行,all接收一个数组参数,里面的值最终都返回Promise对象。

function runAsync1() {
    let p = new Promise((resolve,reject) => {
        setTimeout(function() {
            console.log('异步任务1执行完成');
            resolve('随便什么数据1')
        },2000);
    })
    return p;  
}


function runAsync2() {
    let p = new Promise((resolve,reject) => {
        setTimeout(function() {
            console.log('异步任务2执行完成');
            resolve('随便什么数据2')
        },2000);
    })
    return p;  
}

Promise.all([runAsync1(), runAsync2()])
.then(function(results){
    console.log(results);
});

在这里插入图片描述


4.2.5 Promise.race()

  • Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2,p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
function runAsync1() {
    let p = new Promise((resolve,reject) => {
        setTimeout(function() {
            console.log('异步任务1执行完成');
            resolve('随便什么数据1')
        },2000);
    })
    return p;  
}


function runAsync2() {
    let p = new Promise((resolve,reject) => {
        setTimeout(function() {
            console.log('异步任务2执行完成');
            resolve('随便什么数据2')
        },2000);
    })
    return p;  
}

Promise.race([runAsync1(), runAsync2()])
.then(function(results){
    console.log(results);
});

在这里插入图片描述


4.3 手写一个Promise

4.3.1 Promise对于状态的控制

Promise对于状态控制上的特点:

  • Promise是一个构造函数, 且接受一个函数executor作为参数, 他构造出的实例一开始具有两个属性,一个用来表示当前状态,一个用来表示Promise结果 , 即官方所描述的[[PromiseStatus]]和 [[PromiseValue]]
  • 写在参数executor中的代码会立即执行, 同时函数参数会被传递两个参数, 一个resolve, 一个reject用于处理不同的情况
  • 用户可以在executor中通过执行两个传递进来的参数提前引导Promise走向已决, 执行的方法不同走向的状态也不同,而Promise的状态一旦改变则不可逆转
  • 在executor中如果出现错误, 则会将错误抛出, 并且将状态直接抛向已决的rejected状态

  • step1
// Promise是一个构造函数,为了更好的封闭作用域,需要用到立即执行函数
const MyPromise = (function() {
    // 常量变量定义
    const PromiseStatus = Symbol('PromiseStatus'), // 每个实例上的Promise状态
          PromiseValue = Symbol('PromiseValue'); // 每个实例上的Promise结果
    
    // Promise状态, pedding, resolved, rejected
    const _PEDDING = 'pedding';
    const _RESOLVED = 'resolved';
    const _REJECTED = 'rejected';

    // 将真正的MyPromise构造函数返回出去
    return class MyPromise {
        // excutor:用户传递进来的函数参数 
        constructor(excutor){
            // 每个Promise实例刚出生的时候状态为pedding, 结果为undefined
            this[PromiseStatus] = _PEDDING;
            this[PromiseValue] = undefined;
        }
    }
}());  

阶段性实验代码:

const myPrimose = new MyPromise((resolve,reject) => {

})
console.log(myPrimose);

【JavaScript】回调地狱、Promise_第2张图片


  • step 2
const MyPromise = (function() {
    
    const PromiseStatus = Symbol('PromiseStatus'), 
          PromiseValue = Symbol('PromiseValue'); 
    
    
    const _PEDDING = 'pedding';
    const _RESOLVED = 'resolved';
    const _REJECTED = 'rejected';

    //  resolve和reject方法, 我们在原型上或者构造函数中是看不到的
    // 所以肯定resolve reject方法在闭包中
    const resolve = _data => {

    }

    const reject = _error => {

    }
    
    return class MyPromise {
        
        constructor(excutor){
            this[PromiseStatus] = _PEDDING;
            this[PromiseValue] = undefined;
            // excutor在一开始就会被立马执行, 所以势必是在构造函数中走了一次
            excutor(resolve,reject);
        }
    }
}()); 

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {
    console.log('我会立即执行吗?');
})

console.log(myPrimose);

结果在意料之中,executor中的代码被成功执行
在这里插入图片描述


  • step 3
const MyPromise = (function() {
    
    const PromiseStatus = Symbol('PromiseStatus'), 
          PromiseValue = Symbol('PromiseValue'); 
    
    
    const _PEDDING = 'pedding';
    const _RESOLVED = 'resolved';
    const _REJECTED = 'rejected';

    // 定义一个this指向,避免当前作用域下的this混乱
    let self = null;

    /*
    考虑到改变状态这个操作在resolve、reject中只有参数不同,我们可以抽离成一个方法
    _status: 新的状态值
    _result: 新的value值;
    我们知道用户一旦将状态推向已决, 那么PromiseValue就一定要得出一个结果
    */
    const changePromiseStatus = (_status,_result) => {
        //当前阶段不是已决
        if(self[PromiseStatus] !== _PEDDING) return;
        
        self[PromiseStatus] = _status;
        self[PromiseValue] = _result;
    }


    const resolve = _data => {
        changePromiseStatus(_RESOLVED,_data);
    }

    const reject = _error => {
        changePromiseStatus(_REJECTED,_error);
    }
    
    return class MyPromise {
        
        constructor(excutor){
            // this指向self
            self = this;
            this[PromiseStatus] = _PEDDING;
            this[PromiseValue] = undefined;
            excutor(resolve,reject);
        }
    }
}());   

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {
    console.log('我会立即执行吗?');
    resolve('hello');
})

console.log(myPrimose);


const mySecPromise = new MyPromise((resolve, reject) => {
    console.log('我是第二个Promise实例');
    reject('error');
})

console.log(mySecPromise);

测试结果:
【JavaScript】回调地狱、Promise_第3张图片


  • step 4
// 对状态控制的最后一个处理
const MyPromise = (function() {
    
    const PromiseStatus = Symbol('PromiseStatus'), 
          PromiseValue = Symbol('PromiseValue'); 
    
    
    const _PEDDING = 'pedding';
    const _RESOLVED = 'resolved';
    const _REJECTED = 'rejected';

    
    let self = null;

    const changePromiseStatus = (_status,_result) => {
        
        if(self[PromiseStatus] !== _PEDDING) return;
        
        self[PromiseStatus] = _status;
        self[PromiseValue] = _result;
    }


    const resolve = _data => {
        changePromiseStatus(_RESOLVED,_data);
    }

    const reject = _error => {
        changePromiseStatus(_REJECTED,_error);
    }
    
    return class MyPromise {
        
        constructor(excutor){
            self = this;
            this[PromiseStatus] = _PEDDING;
            this[PromiseValue] = undefined;

            // 如果我们在executor中报错, 则会直接触发reject从而进入rejected状态, 
            // 并将错误信息传递给reject方便后续处理
            // 所以我们势必需要try catch捕捉一下错误
            try{
                excutor(resolve,reject);
            }catch(error){
                reject(error)
            }
        }
    }
}());   

阶段性测试:

const myPrimose = new MyPromise((resolve,reject) => {
    console.log('我会立即执行吗?');
    resolve('hello');
})

console.log(myPrimose);

const mySecPromise = new MyPromise((resolve, reject) => {
    console.log('我是第二个Promise实例');
    console.log(abc);   // 未声明会报错
})

console.log(mySecPromise);

毫无意外, 结果如下, 如果在executor中报错, 则会直接进入rejected状态

在这里插入图片描述


4.3.2 Promise的后续处理(then catch)

  • then和catch是属于原型上的方法, then方法接收两个参数, 第二个参数可以不传, 这两个参数分别是成功后的回调和失败后的回调,catch方法接收一个参数, 为失败后的回调。
  • Promise串联: 我们知道Promise的then和catch是可以链式调用的, 且下一次Promise的then结果是上一次Promise then或者catch的返回值,如果上一次Promise的执行过程中没有出错, 那么不管结局是resolved或者rejected,新的Promise都会走向resolved, 反之rejected。

const MyPromise = (function() {

    const PromiseStatus = Symbol('PromiseStatus'), 
        PromiseValue = Symbol('PromiseValue'); 

    const _PEDDING = 'pedding';
    const _RESOLVED = 'resolved';
    const _REJECTED = 'rejected';

    // 用来存储Promise.then加入的回调
    const fullFilledList = Symbol('fullFilledList');
    // 用来存储Promise.catch
    const rejectedList = Symbol('rejectedList'); 

    // 这个方法时用来处理用户调用then和catch方法时的处理函数
    // _status: 要判定的状态, handler: 当前处理函数, queue: 当前处理函数队列
    const settleHandler = (_status, handler, queue) => {
        if(self[PromiseStatus] === _status) {
            setTimeout(() => handler(self[PromiseValue]), 0);
        }
        else queue.push(handler);  
    }

    let self = null; 

    const changePromiseStatus = (_status,_result) => {
        if(self[PromiseStatus] !== _PEDDING) return;
        self[PromiseStatus] = _status;
        self[PromiseValue] = _result;
        
        // 一旦用户更改了状态, 所有在then和catch中对应的方法都要执行
        if(self[PromiseStatus] === _RESOLVED) self[fullFilledList].forEach(ele => ele(self[PromiseValue]))
        else self[rejectedList].forEach(ele => ele(self[PromiseValue]))
    
    }


    const resolve = _data => {
        changePromiseStatus(_RESOLVED, _data);
    }

    const reject = _error => {
        changePromiseStatus(_REJECTED, _error);
    }


    return class MyPromise {
        constructor(executor) {
            self = this;
            this[PromiseStatus] = _PEDDING;
            this[PromiseValue] = undefined;

            this[fullFilledList] = [];
            this[rejectedList] = [];
            
            try {
                executor(resolve, reject);
            }catch(error) {
                reject(error);
            }
        }


        // then 和catch方法是在原型上出现的
        // then方法接受一个thenbale: 成功回调函数和一个catchable: 失败回调函数
        then = (thenable, catchable) => {

            // 我们知道, 如果当前状态已经为已决阶段的两种状态了, 那么回调函数
            // 会被立即执行, 否则才会放入相应数组

            // 设置相应状态
            settleHandler('resolved', thenable, this[fullFilledList])
            // 因为catchable很有可能不传递, 所以必须容错
            typeof catchable === 'function' &&  this.catch(catchable);
        }

        // catch方法只接受一个catchable失败回调函数
        catch = (catchable) => {
            settleHandler('rejected', catchable, this[rejectedList]) 
        }
    }

}()) 

阶段性测试:

const myPromise = new MyPromise((resolve, reject) => {
    console.log('我会立即执行吗?');
    reject('hello');
})

myPromise.then((data) => {
    console.log(data);
}, (err) => {
    console.log(err);
})

console.log(myPromise);

4.3.3 Promise.all()的实现

Promise.all 接收一个 promise 对象的数组作为参数,当这个数组里的所有 promise 对象全部变为resolve或 有 reject 状态出现的时候,它才会去调用 .then 方法,它们是并发执行的。

promise.all()特点解读:

  1. Promise.all()方法可以将多个Promise实例包装成为一个Promise对象§,接收一个数组作为参数,数组中不一定需要都是Promise对象,但一定具有Iterator接口。如果不是,调用Promise.resolve将其转化为Promise对象之后再进行处理。

  2. 使用Promise.all()生成的Promise对象§的状态由数组中的Promise对象(p1,p2,p3)决定:

    • 如果所有的Promise对象(p1,p2,p3)都变成fullfilled状态的话,生成的Promise对象§也会变成fullfilled状态,p1,p2,p3三个Promise对象产生的结果会组成一个数组返回给传递给p的回调函数
    • 如果p1,p2,p3中有一个Promise对象变为rejected状态的话,p也会变成rejected状态,第一个被rejected的对象的返回值会传递给p的回调函数。

手写实现自己的Promise.all()

  1. 版本一
function PromiseAll(arr) {
    //PromiseAll的返回值为一个Promise对象
    return new Promise((resolve,reject) => {
        //传入的PromiseAll必须是一个数组
        if(!Array.isArray(arr)){
            return reject(new TypeError('arr must be an array.'));
        };

        let resArr = [];
        for(let i in arr) {
            (function(i){
                Promise.resolve(arr[i]).then(res => {
                    resArr.push(res);
                    if(i == arr.length-1){
                        return resolve(resArr);
                    }
                },err => {
                    return reject(err);
                }).catch(err => {
                    console.log(err);
                })
            })(i)
        }
    })
}
  1. 版本二
function PromiseAll(arr) {
    return new Promise((resolve,reject) => { // 返回一个新的Promise对象
        let resArr = [];  // 定义一个空数组存放结果
        let i = 0;
        function handleData(index,data){ // 处理数据函数
            resArr[index] = data;
            i++;
            if(i == arr.length){  //当i等于传递的数组的长度时 
                resolve(resArr);  //执行resolve,并将结果放入
            }
        }

        for(let i=0;i<arr.length;i++) {  //循环遍历数组
            Promise.resolve(arr[i]).then((data) => {
                handleData(i,data);  //将结果和索引传入handleData函数
            })
        }
    })
}

测试:

// 测试
const pro1 = new Promise((res,rej) => {
    setTimeout(() => {
        res('1')
    },1000)
})
const pro2 = new Promise((res,rej) => {
    setTimeout(() => {
      res('2')
    },2000)
  })
const pro3 = new Promise((res,rej) => {
    setTimeout(() => {
      res('3')
    },3000)
})

const proAll = PromiseAll([pro1,pro2,pro3])
.then(res => {
    console.log(res);// 3秒之后打印 ["1", "2", "3"]
}).catch((e) => {
    console.log(e);
})

在这里插入图片描述


4.3.4 Promise.race()的实现

function PromiseRace(arr) {
    return new Promise((resolve,reject) => {
        if(!Array.isArray(arr)){
            return reject(new TypeError('arguments must be Array'));
        };

        for(let i=0;i<arr.length;i++) {
            Promise.resolve(arr[i]).then(data => {
                resolve(data);
            },err => {
                reject(err);
            })
        }
    })
}

测试:

// 测试
const pro1 = new Promise((res,rej) => {
    setTimeout(() => {
        res('1')
    },1000)
})
const pro2 = new Promise((res,rej) => {
    setTimeout(() => {
      res('2')
    },2000)
  })
const pro3 = new Promise((res,rej) => {
    setTimeout(() => {
      res('3')
    },3000)
})

const proAll = PromiseRace([pro1,pro2,pro3])
.then(res => {
    console.log(res);	// 输出1
}).catch((e) => {
    console.log(e);
})

在这里插入图片描述

手写Promise参考博客

你可能感兴趣的:(JavaScript基础,javascript,前端)