Promise的详解,解决回调地狱

1、作用

  • 解决回调地狱问题

2、Promise的参数

  • Promise是一个构造函数,通过new关键字实例化对象
  • 接收一个函数作为参数
    • 在接收的 作为参数的函数 中,又接收两个参数:resolve 和 reject
    • resolve 和 reject 又是函数作为参数

3、Promise的状态 :state

  • pending状态:准备、待决解、进行中
  • fulfilled状态:已完成、成功
  • rejected状态:已拒绝、失败

4、状态的改变

  • 初始化的时候,当前Promise的状态:pending

  • 调用作为参数的函数来改变:

    • 调用resolve(); 将当前Promise的状态改变成:fulfilled
    • 调用reject(); 将当前Promise的状态改变成:rejected
  • Promise执行体中出现运行错误,也会改变状态:

    • 出现运行错误,当前Promise的状态改变成:rejected
  • 主动抛出错误时,也会改变状态:

    throw new Error('主动抛出一个error');
    
    • 当前Promise的状态改变成:rejected
  • 注:Promise的状态改变是一次性的,也就是状态的改变只有一个结果,不会切换。要么是fulfilled,要么是rejected

  • 在Promise执行体中先调用 resolve(); 再调用 reject(); Promise的状态也只会改变一次,不会先后切换。

5、Promise的结果:result 属性

  • 已知:Promise接收一个参数(函数类型),该函数又接收两个参数:resolve和reject,这两者也是函数类型。

  • 既然是函数,那么就意味着可以传参。

    resolve('成功的传参');
    reject('失败的传参');
    
  • 从第4点可知,调用resolve,会改变Promise的状态。并且调用resolve会改变Promise的结果,而这个结果就是resolve函数中传进的参数。

  • 当调用 resolve(‘成功的传参’); Promise的 state: fulfiiled,会将resolve的参数赋值给Promise的result,result: ‘成功的传参’

  • 同理:reject();

  • 结论:Promise的结果改变是通过调用resolve()和reject()来传递参数 或者其他异常情况使得Promise的状态改变,传递错误信息 来实现的。

6、Promise的方法

  • 方法的查看:因为Promise是一个构造函数,可以在__proto__上查看它拥有的方法。

6.1、then()方法

  • 参数:then方法接收两个参数,这两个参数都是函数类型

  • 第一个参数:成功回调函数

    .then(
    	()=>{
          
           console.log('成功时回调'); }
    )
    
  • 第二个参数:失败回调函数

    .then(
    	()=>{
          
           console.log('成功时回调'); },  // 要加逗号把两个参数分隔开
        
        ()=>{
          
           console.log('失败时回调'); }
    )
    
  • 参数函数的 触发时机:

    • 成功回调函数,顾名思义,当Promise调用resolve(); state就会变成fulfilled,当状态变成fulfilled,就会触发.then()里面的成功回调函数。也就是第一个参数(函数类型)会被调用。
    • 失败回调函数,同理,当Promise执行体执行出错或调用reject(); state就会变成rejected,当状态变成rejected,就会触发.then()里面的失败回调函数。也就是第二个参数(函数类型)会被调用。
    • 如果Promise的state 保持 pending,那么是不会触发.then()里面的方法的。
      • 必须有状态改变才会触发
      • 改变:pending->fulfilled/rejected
      • 不改变:pending->pending

6.1.1、then()方法 参数详解

  • 从上文可知,then的参数数量:2个,类型:函数

  • 触发时机:Promise的状态变成:fulfilled / rejected

  • 既然是函数,那么就可以往里面传递参数。

    • 参数来源:触发时机的源头
      • 细细分析,从6.1的触发时机可知,Promise的状态改变,都是从调用resolve/reject 开始,调用了resolve,state就变成:fulfilled,此时Promise的result就接收到resolve中的参数。参考:第5点。
      • 此时Promise的result就作为参数传递给了.then();
        • state:fulfilled,接收到的参数是来自于:resolve()
        • state:rejected,接收到的参数是来自于:rejected() 或者 执行出错error
    new Promise( (resolve, reject)=>{
          
          
        $.ajax({
          
          
            type: 'GET',         // 请求方式
            url: 'xxx.xxx',		 // 请求url
            data:{
          
          },			// 带的请求参数
            
            // 请求成功回调
            success: res=>{
          
          		
    		   // 如果ajax请求成功,就会得到数据:res
                // 此时调用resolve将Promise的state改变成:fulfilled
                // resolve携带【实参】改变当前Promise的result,再传递给.then()
                resolve(res);    
            },
            
            // 请求失败回调
            error: err=>{
          
                  
                // 如果ajax请求失败,就会得到error信息
                // 此时调用reject将Promise的state改变成:rejected
                // reject携带【实参】改变当前Promise的result,再传递给.then()
                reject(err);
            }
        })
    })
    .then(
        // fulfilled的时候,从Promise的result获取到实参res,赋给value
    	(value)=>{
          
           console.log('成功时回调', value); },
        
        // rejected的时候,从Promise的result获取到实参error信息,赋给reason
        (reason)=>{
          
           console.log('失败时回调', reason); }
    )
    

    6.1.2、then()方法的返回值

    • then是同步的,但then里面的两个参数(函数类型)是异步的,只有等到Promise的state改变,才会触发回调

    • 奇妙的地方:如果用一个变量来保存.then()的返回值,打印出来的不是一个值,而是一个新的Promise

      • 也就是说,then()返回出去的是一个新的Promise (初始化state:pending)
    • 由于返回出去的是一个新的Promise,那么就意味着它也拥有state、result、then…,那么就可以进行:链式操作(让代码扁平化,不陷入回调地狱)

      new Promise( (resolve,reject)=>{
              
              } ).then().then().then()...
      

    6.1.3、修改then的返回值(一个新的Promise)的状态

    • 假设:初始的Promise调用的是resolve(),那么**第一个.then()**触发的是成功回调(第一个参数)
      • 在第一个.then()的成功回调中 返回出去一个值 可将当前then的返回值(一个新的Promise)的状态从pending变成fulfilled
      • 返回出去的值 将作为实参传递给第二个.then()
    new Promise( (resolve,reject)=>{
          
          
        resolve('成功的传参');
    })
    .then(
    	(value1)=>{
          
           
            console.log('成功1时回调', value1); 
    	    // 返回出去一个值,将改变下一个Promise的状态
            // 让下一个then触发对应参数函数,并获取到实参
            return '成功1';
        })
    .then(
        // 第二个then触发成功回调
        // 获取到上一个then传递的【成功1】,赋给value2
        (value2)=>{
          
           
        	console.log('成功2时回调', value2);
        	return '成功2';
        })
    // 以此类推,无限链式操作
    .then(...)
    .then(...)
    ...
    
    • 对于执行出错触发的失败回调也可以通过 return出去一个值 改变当前then的返回值(一个新的Promise)的状态,从pending变成fulfilled,触发的是下一个then的成功回调
    new Promise( (resolve,reject)=>{
          
          
        reject('失败的传参');
    })
    .then(
    	(value1)=>{
          
           
            console.log('成功1时回调', value1);
        },
        // 触发失败回调,接收到reason1 =【实参:'失败的传参'】
    	(reason1)=>{
          
          
            console.log('失败1时回调', reason1);
            // 这里可以return 出去一个值,将当前then的状态改变成fulfilled
            // 触发下一个then的【成功回调】
            return '第一个then失败回调传参';
        }
    )
    .then(
        // 第二个then触发成功回调
        // 获取到上一个then传递的实参:value2 =【第一个then失败回调传参】
        (value2)=>{
          
           
        	console.log('成功2时回调', value2);
        	return '成功2';
        },
        // 不会被调用,除非有执行错误
    	(reason2)=>{
          
          
            console.log('失败2时的回调', reason2);
        }	
    )
    // 以此类推,无限链式操作
    .then(...)
    .then(...)
    ...
    
    • 结论:无论在then的 成功回调 还是 失败回调 中return出去一个值,都会使当前then的返回值(一个新的Promise)的state从pending改变成fulfilled,从而触发下一个then的对应回调。只要return不断,那么就可以持续链式操作下去。

6.2 、catch方法

6.2.1、执行时机

  • catch在以下几种情况,会触发执行:
    • 代码执行错误;
    • 主动调用reject();
    • 主动抛出错误
  • 也就是:当Promise的state:rejected的时候,就会触发到catch();

6.2.2、catch的参数

  • catch()接收一个参数(函数类型),当Promise的state:rejected的时候,会执行catch()里面的参数(函数)
    • 由于参数是函数类型,那么该参数也可以传参进去。
    • 结合6.2.1的执行时机,可以知道,传进去的实参,就是:
      • 代码执行错误信息
      • state变成rejected的原因
      • 主动抛出的错误信息

7、Promise的常见写法

  • 一般使用catch来捕获任何异常
  • 在then中只写成功回调
new Promise( (resolve,reject)=>{
    
    
    //Promise执行体
    resolve('成功的传参');
    reject('失败的传参');
})
.then(
	(value)=>{
    
     
        console.log('成功回调', value); 
    })
.catch(
	(reason)=>{
    
    
     	console.log('失败回调/捕获异常', reason);
    })

8、Promise解决回调地狱

8.1、回调地狱

  • 后一次的请求依赖于前一次请求的结果,所以只能在前一次请求成功的回调中进行后一次请求;
  • 在第一个请求成功的回调里进行第二个请求,在第二个请求成功的回调里进行第三个请求,在第三个请求成功…
// ------第一次请求------
$.ajax({
    
    
    type: 'GET',
    url: '1.data',
    data: {
    
    },
    success: res =>{
    
    
        let {
    
    id} = res;  // 用变量存储请求到结果
        // ------第二次请求------
        $.ajax({
    
    
    		type: 'GET',
   		 	url: '2.data',
    		data: {
    
    id},  // 请求的参数依赖前一次请求结果
    		success: res =>{
    
    
                let {
    
    username} = res  // 用变量存储请求到结果
        		// -------第三次请求------
        		$.ajax({
    
    
                    type: 'GET',
                    url: '3.data',
                    data: {
    
    username},  // 请求的参数依赖前一次请求结果
                    success: res =>{
    
    
                        console.log(res);  // ...可以停了,不想回调下去了
                    },
                    error: err =>{
    
     // 第三次请求的失败回调
                        console.log(err)
                    }
                })
    		},
    		error: err =>{
    
     // 第二次请求的失败回调
        		console.log(err);
    		}
		})
    },
    error: err =>{
    
     // 第一次请求的失败回调
        console.log(err);
    }
})

8.2、解决回调地狱 (先不用catch)

new Promise((resolve, reject) => {
    
    
  //------第一次请求------
  $.ajax({
    
    
    type: 'GET',
    url: '1.data',
    data: {
    
    },
    success: res => {
    
    
      // 改变 第一个Promise的状态,pending->fulfilled
      // 把请求结果传递给 第一个then
      resolve(res);
    },
    error: err => {
    
    
      // 第一次请求的失败回调
      // 改变 第一个Promise的状态,pending->rejected
      // 把错误信息传递给 第一个then
      reject(err);
    },
  });
})
  // 第一个then
  .then(
    // 第一个Promise:fulfilled回调
    value => {
    
    
      let {
    
     id } = value; // 用变量存储请求到结果
      // ------第二次请求------
      // return 的新Promise会被下一个then接收到
      return new Promise((resolve, reject) => {
    
    
        $.ajax({
    
    
          type: 'GET',
          url: '2.data',
          data: {
    
     id }, // 请求的参数依赖前一次请求结果
          success: res => {
    
    
            // 将第二次请求成功的结果返回出去给第二个then接收
            // 并改变当前Promise的state:pending->fulfilled
            resolve(res);
          },
          error: err => {
    
    
            reject(err);
          },
        });
      });
    },
    // 第一个Promise:rejected回调
    reason => {
    
    
      console.log(reason);
    }
  )
  // 第二个then
  .then(
    // 第二个Promise:fulfilled回调
    value => {
    
    
      let {
    
     username } = value; // 用变量存储请求到结果
      // ------第三次请求------
      return new Promise((resolve, reject) => {
    
    
        $.ajax({
    
    
          type: 'GET',
          url: '3.data',
          data: {
    
     username }, // 请求的参数依赖前一次请求结果
          success: res => {
    
    
            consle.log(res); // ...可以停了,不想回调下去了
          },
          error: err => {
    
    
            console.log(err);
          },
        });
      });
    },
    // 第二个Promise:rejected回调
    reason => {
    
    
      console.log(reason);
    }
  );

8.3、代码优化

  • 可以看到,用Promise的写法与普通回调的写法相比,代码更加结构清晰,扁平化,易读,嵌套的层数比较少。
  • 但是也可以看到代码中存在大量重复性代码,那么就可以进行重复部分的封装,让代码更加简洁明了。
// 封装的函数返回一个Promise实例,在后续的使用可以直接使用.then来获取请求得到的数据了
function ajaxReq(type, url, data={
    
    }){
    
    
    return new Promise( (resolve, reject)=>{
    
    
        $.ajax({
    
    
        	type:type,
        	url: url,
        	data:data,
        	success: res =>{
    
    
            	resolve(res);
        	},
  			error: err =>{
    
    
            	reject(err);
        	}          
    	})
  	})
}

// 因为ajaxReq返回的是一个Promise实例,那么直接用.then来获取数据
// ------第一次请求------
ajaxReq('GET', '1.data')
    // 第一次处理
    .then(
	(value)=>{
    
    
        let {
    
    id} = value;
        // ------第二次请求------
        return ajaxReq('GET', '2.data', {
    
    id})
    })
	// 第二次处理
    .then(
     (value) =>{
    
    
         let {
    
    username} = value;
         // ------第三次请求------
         return ajaxReq('GET', '3.data',{
    
    username})
     })
	// 第三次处理
	.then(
	 (value) =>{
    
    
         console.log(value);  // ...可以停了,不想回调下去了
     })

猜你喜欢

转载自blog.csdn.net/michaelxuzhi___/article/details/118072834
今日推荐