Promise详解

837 阅读11分钟

1.什么是Promise

Promise的诞生就是为了解决异步请求中的回调地狱问题,它是一种设计模式,ES6中提供了一个JS内置类Promise,来实现这种设计模式;

2.Promise语法

`new Promise([executor])`第一执行函数必须传递;
`let promiseExamp = new Promise()`; //=>Uncaught TypeError: Promise resolver undefined is not a function

new Promise( function(resolve, reject) {...} /* executor */  );

executor是带有 resolvereject 两个参数的函数,executor执行函数是必须传递的 。

Promise是用来管理异步编程的,它本身不是异步的;

1.new Promise的时候回立即把executor函数执行(只不过我们一般会在executor函数中处理一个异步操作)
2. Promise不仅把EXE执行,而且还给EXE传递两个参数(两个参数也是函数类型);
	* resolve函数 : 它执行代表Promise处理的异步事情是成功的,把Promise的状态给改为fulfilled
	* reject函数:它执行代表Promise处理的异步事情是失败的,把Promise的状态改为rejected
3. EXE函数中放的就是当前要处理的异步操作事情
/*
* new Promise的时候先执行executor函数,在这里开启一个异步操作的任务
(此时不等:把其放入到EventQue任务队列中),继续执行
p1.then基于then方法,存储起来两个函数(此时这两个函数还没有执行);当executor函数中的异步操作结束了,基于resolve/reject控制Promise状态,而决定执行then存储的函数中执行哪一个
*/
let promiseExamp = new Promise((resolve,reject)=>{
// 这里一般存放的都是我们即将要处理的异步任务,任务成我们执行resolve,任务失败我们执行reject(当然写同步的也可以);
    let ran = Math.random();
       setTimeout(()=>{
           if(ran < 0.5){
               reject(ran);
               return;
           }
           resolve(ran);
       },1000)
});

promiseExamp.then(result => {
	//=>状态为FULFILLED成功后执行(RESULT:[[PromiseValue]])
	console.log('成功: ' + result);
}, error => {
	//=>状态为REJECTED失败后执行
	console.log('失败: ' + error);
}); 
/*
resolve/reject的执行,不论是否放到一个异步操作中,都需要等待then先执行完,把方法存储好,才能更改状态后执行then中对应的方法=> 此处是一个异步操作(所以有人说promise是异步的);
*/  

3.Promise状态

Promise对象代表一个异步操作,有三种状态:

  • pending(进行中) [初始状态] New Promise后的状态;
  • fulfilled(已成功)
  • rejected(已失败)
// 创建Promise实例
let promiseExamp = new Promise([executor]); 
console.log(promiseExamp);
//  [[PromiseStatus]]: "pending" - "fulfilled"   // "pending" - "rejected"
//  [[PromiseValue]]: undefined

/*
* 1.创建Promise实例,执行[executor];
* 2.把函数中异步操作执行,设置一个定时器,1000MS后执行定时器中方法;
 	* 等待异步操作完成...
 	* 异步等待过程先执行下面代码=>
	  * 基于then方法,构建成功或者失败后要做的事情(只是把方法放到事件池中)
		* 当(异步操作完成后)状态改变后,会通知事件池执行哪个;
		* 构建一个事件池,A成功后执行的方法,B失败后执行的方法;
	    
* 3.在异步操作完成后,通过执行resolve/reject修改pro状态(假设当前案例中执行的是resolve,状态改变为“fulfilled”; reject执行,状态改为“rejected”;(状态改了之后就会通知事件池应该执行哪个方法了)
* 4.resolve和reject 在执行的时候,我们可以给其传参,传递的参数是会修改[PromiseValue];
*/

4. Promise的链式写法

/*
* THEN方法结束都会返回一个新的Promise实例
*   p1这个new Promise出来的实例,成功或失败,取决于executor函数执行的是resolve还是reject决定的,
再或者executor函数执行发生异常错误,也会把状态改成失败的;
*   
*   p2及以后每次执行,then返回的新实例的状态,由then中存储的方法执行的结果来决定最后的状态(上一个then中某个方法执行的结果决定then中哪个方法被执行);
        =>无论是成功还是失败的方法执行(then中的两个方法),凡是抛出异常则都会把实例的状态改为失败
        =>方法中如果返回一个新的Promise实例,返回这个实例的结果是成功还是失败也决定了当前实例是成功还是失败
        =>剩下的情况基本上都是让实例变为成功的状态(方法返回的结果是当前实例的value值,上一个then会传递到下一个then的方法中);
*/

let p1 = new Promise((resolve, reject) => {
    reject(100);
});

let p2 = p1.then(result => {
    console.log(`成功:` + result);
    return 200;
}, reason => {
    console.log(`失败:` + reason);
    return -10;
}).then(result => {
    console.log(`成功:` + result);
}, reason => {
    console.log(`失败:` + reason);
});

==============
let p1 = new Promise((resolve, reject) => {
    resolve(a);  // 报错
});

let p2 = p1.then(result => {
    console.log(`成功:${result}`);
    return result * 10;
}, reason => {
    console.log(`失败:${reason}`);
}).then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
});

==========
Promise.resolve(10).then(result => {
    console.log(`成功:${result}`);
    return Promise.reject(result * 10);
}, reason => {
    console.log(`失败:${reason}`);
}).then(result => {
    console.log(`成功:${result}`);
}, reason => {
    console.log(`失败:${reason}`);
});

=============
/*遇到一个then,需要执行成功或失败的方法,如果此方法并没有在当前then中被定义,则顺延到下一个对应的函数;*/

Promise.resolve(10).then(result => {
    console.log(`成功:${result}`);
    return result * 10;
}).then(null, reason => {
    console.log(`失败:${reason}`);
});

//.catch 等同于上面的写法
Promise.resolve(10).then(result => {
    console.log(`成功:${result}`);
    return result * 10;
}).catch(reason => {
    console.log(`失败:${reason}`);
});

5.Promise方法

Promise.prototype

1.then

2.catch

3.finally

then: 设置成功或失败后执行的方法;(成功或者失败都可以设置,也可以只设置一个);

​ pro.then([success],[error]);

​ pro.then([success])

​ pro.then(null,[error]);

catch : 设置失败后执行的方法;

finally : 设置不论成功还是失败都会执行的方法(一般不用)

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

// then语法  =>.then(onResolve.,onReject);
/*注意: then不是直接执行,而是把方法放到是事件池中,等异步操作完成后返回状态再执行;
* Promise.prototype
*    then:设置成功或者失败后执行的方法(成功或者失败都可以设置,也可以只设置一个)
*       pro.then([success],[error])
* 		 pro.then([success])
*       pro.then(null,[error])
*/

// 小例子
let promiseExamp = new Promise((resolve, reject) => {
			setTimeout(() => {
				let ran = Math.random();
				ran < 0.5 ? reject(ran) : resolve(ran);
			}, 1000);
});

promiseExamp.then(result => {
    //=>状态为FULFILLED成功后执行(RESULT:[[PromiseValue]])
    console.log('成功: ' + result);
}, error => {
    //=>状态为REJECTED失败后执行
    console.log('失败: ' + error);
}); 

//还是拿这个例子来说,如果成功输出console.log('成功: ' + result);失败 console.log('失败: ' + error);


//=>
promiseExamp.then(result => {
	console.log('成功: ' + result);
}).catch(error => {
	console.log('失败: ' + error);
}).finally(x => {
	console.log('哈哈');
});

成功=>console.log('成功: ' + result); 失败 	console.log('失败: ' + error); 不管成功还是失败 都会输出 console.log('哈哈');

/* 执行THEN/CATCH/FINALY方法返回的结果是一个全新的Promise实例
*	所以可以链式写下去:下一个THEN中哪个方法会被执行,由上一个THEN中方法执行的结果执行;
*	上一个THEN中某个方法的返回值会传递给下一个THEN的某个方法中,不return会展示undefined;

*/
let pro1 = new Promise((resolve, reject) => {
			setTimeout(() => {
				let ran = Math.random();
				ran < 0.5 ? reject(ran) : resolve(ran);
			}, 1000);
});

let pro2 = pro1.then(result=>{
    console.log('PRO1:SUCCESS');
},error=>{
    console.log('PRO1:ERROR');
})

let pro3= pro2.then(result=>{
    console.log('PRO2:SUCCESS');
},error=>{
    console.log('PRO2:ERROR');
});

// *** 以上写法可以简化为:
new Promise((resolve, reject) => {
		resolve(100);
		reject(-100);
}).then(result=>{
// 第一次看执行resolve ,就输出result,执行reject,输出error
    console.log(result);
     return result*10;
},error=>{
    console.log(error);
    return error /10;  //=> THEN中return的结果相当于把这个新的Promise事例中的value值改为返回值,相当于传递给下一个方法实参,不传为undefined
}).then(A=>{  // 第二次及以后执行,不看上一次是哪个函数执行,只看看上一次执行是否成功还是失败(失败指的是是否有报错),不报错执行A,报错执行B;
     console.log('A:'+ A); 
},B=>{
    console.log('B:'+B);
});


// ========
new Promise((resolve, reject) => {
			resolve(100);
    		// reject(-100);
}).then(A=>{
    console.log(A);
     return A*10;
}).catch(B=>{
     console.log(B);
     return B/10;
})

// =======
// 如果当前Promise实例的状态确定后,都会到对应的THEN中找方法,如果THEN中没有对应的方法,则会向下顺延;
new Promise((resolve, reject) => {
			resolve(100);
    		// reject(-100);
}).then(A=>{
    console.log(AAA);  // 找不到AAA,执行报错,让.THEN创建的Promise实例变为失败状态,并且把报错的原因修改下次Promise的value值;
     return A*10;
}).catch(B=>{
     console.log(B);  //  ReferenceError: AAA is not defined
     return '@';   
}).then(C=>{
    console.log(C);  // @  上一个正常执行没有报错就可以执行;
});

//==============
new Promise((resolve, reject) => {
	resolve(100);
	// reject(-100);
}).then().catch(x => {
	console.log(1);
}).then(x => {
	console.log(2);  // 2
}).then(x => {
	console.log(3);  // 3 
}).catch(x => {
	console.log(4);
}).then(x => {
	console.log('AAA');  // AAA
	console.log(AAA);
}).catch().then(null, x => {
	console.log(5);   // 5
});


/* 从上述例子来看,我们总结下:
JS中当前代码报错,会中断主线程的渲染(下面代码将不再执行)

throw new Error(' ') : 手动抛出一个异常错误,目的就是让后面的代码不再执行

如果上面代码报错,不想让其影响后面的代码,我们需要做异常捕获:try catch finally
*/

console.log(a); //=>Uncaught ReferenceError: a is not defined
let b = 10;
console.log(b);

// 使用 try{}catch写法,就算报错也不回阻断下面的代码执行
try{
	console.log(a);  
} catch(e){
	console.log(e.message);  // a is not defined
}

let b = 10;
console.log(b);  // 10
Promise.all(arr)

返回结果是一个Promise实例(all实例),要求arr数组中的每一项都是一个新的promise实例,promise.all是等待所有数组中的是状态都为成功才会让”arr实例“状态为成功,value是一个集合,存储这arr中每个实例返回的结果,凡是arr中有一个实例状态为失败,"arr实例"的状态也是失败;

Promise.race(arr)

和all不同的地方:race是赛跑,也就是arr不管哪一个先处理完,处理完的结果作为“race实例”的结果;

let p1 = Promise.resolve(1);
let p2 = new Promise(resolve => {
    setTimeout(_ => {
        resolve(2);
    }, 1000);
});
let p3 = Promise.reject(3);

Promise.all([p1, p2, p3]).then(result => {
    console.log(`成功:${result}`);
}).catch(reason => {
    console.log(`失败:${reason}`);
});

/* ES7中提供了promise操作的语法糖:async/await
*	async 是让一个普通函数返回的结果变为status=resolved并且value=return结果的promise实例
*	async 最主要的作用是配合await使用的,因为一旦在函数中使用await,那么当前函数必须用async修饰
*/

async function fn(){
    console.log(1);
    // await会等待当前promise的返回结果,只有返回状态为resolve情况,才会把返回结果赋值给result;
    // await不是同步编程,是异步编程(微任务):当代码执行到此行,构建一个异步的微任务(等待promise返回结果,并且await下面的代码也都被列到任务队列正);
    let result = await p1;
    console.log(result);

    let AA = await p2;
    console.log(AA);
}
fn();
console.log(2);

---------------
async function fn() {
    //如果promise是失败状态,则await不会接收其他返回结果,await下面的代码也不会继续执行(await只能处理promise为成功状态的情况);
    let result = await p3;
    console.log(result);
}
fn();

2. 练习题

async function async1(){
    console.log(`async1 start`);
    await async2();
    console.log(`async1 end`);
}
async function async2(){
    console.log(`async2`);
};
console.log(`script start`);
setTimeout(function(){
    console.log('setTimeout');
},0);
async1();
new Promise(function(resolve){
    console.log('promise1');
    resolve();
}).then(function(){
    console.log('promise2');
})
console.log('script end');

promise同步异步

解决回调地狱

/*Promise.all([Promise1,Promise2,Promise3,...]) 
*	 Promise.all([promise1,promise2,...]):ALL中存放的是多个PROMISE实例(每一个实例管理者一个异步操作),执行ALL方法返回的结果是一个新的PROMISE实例"PROA"
*   当所有PROMISE实例的状态都为Fulfilled的时候(成功),让PROA的状态也变为Fulfilled,并且把所有PROMISE成功获取的结果,存储为成为一个数组(顺序和最开始编写的顺序一致)“result=[result1,result2,...]”,让PROA这个数组的VALUE值等于这个数组
*   都成功(PROA状态是FUFILLED)才会通知THEN中第一个方法执行,只要有一个失败(PROA状态是REJECTED),就会通知THEN中第二个方法或者CATCH中的方法执行*/

Promise.all([ajax1(), ajax3(), ajax2()]).then(results => {
			//=>results:[result1,result3,result2]
});


Promise.race([ajax1(), ajax3(), ajax2()]).then(result => {
			//=>看哪一个PROMISE状态最先处理完(成功或者失败),以最先处理完的为主
});

Promise 可以解决异步编程中的“回调地狱”

那我们先来看看异步编程中的“回调地狱”

  • AJAX的串行

只有第一个请求成功,才能执行第二个,第二个成功才能执行第三个...最后一个请求成功后拿到每一次请求的所有数据

$.ajax({
	url:'/baseInfo',
	method:'GET',
	data:{
		name:'zhanglu'
	},
	success:result=>{
		let scoreId=result.scoreId;

		$.ajax({
			url:'/scoreInfo',
			method:'GET',
			data:{
				id:scoreId
			},
			success:result=>{
				let chinese=result.chinese;

				$.ajax({
					url:'/paiming',
					method:'GET',
					data:{
						num:chinese
					},
					success:result=>{


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

queryBase().then(baseInfo => {
		let scoreId = baseInfo.scoreId;
		//=>THEN方法中如果返回的是一个PROMISE实例,则当前返回实例的成功或者失败状态,影响着下一个THEN中哪个方法会被触发执行;如果返回的是非PROMISE实例,则看当前方法执行是否报错,来决定下一个THEN中哪个方法执行;
		return queryScore(scoreId);
	}).then(scoreInfo => {
		let chinese = scoreInfo.chinese;
		return queryChinese(chinese);
	}).then(pai => {
		console.log('排名是:' + pai);
	});
	
// => 简写
queryBase().then(baseInfo => queryScore(baseInfo.scoreId))
.then(scoreInfo => queryChinese(scoreInfo.chinese))
.then(pai => {
	console.log('排名是:' + pai);
}); 


// 使用async函数解决
async function func() {
	let baseInfo = await queryBase();
	let scoreInfo = await queryScore(baseInfo.scoreId);
	let pai = await queryChinese(scoreInfo.chinese);
	//....
}
func();



// 串行解决
function queryBase() {
    return new Promise(resolve => {
        $.ajax({
            url: '/baseInfo?name=lanlan',
            success: result => {
                resolve(result);
            }
        })
    });
}

function queryScore(scoreId) {
    return new Promise(resolve => {
        $.ajax({
            url: '/score?scoreId='+scoreId,
            success: result => {
                resolve(result);
            }
        })
    });
}

function queryChinese(chinese) {
    return new Promise(resolve => {
        $.ajax({
            url: '/paiming?chin='+chinese,
            success: result => {
                resolve(result);
            }
        })
    });
}

queryBase().then(baseInfo=>queryScore(baseInfo.scoreId))
  • AJAX的并行

三个请求可以同时发送,但是需要等到所有请求都成功会做一件事情;

//=>三个请求可以同时发送,但是需要等到所有请求都成功才会做一件事
let chi=100,
	eng=12,
	math=98;
let chiPai,
	engPai,
	mathPai;
let count=0;
function func(){
	if(count>=3){
		//=>处理自己要做的事情
	}
}

$.ajax({
	url:'/pai?chi='+chi,
	success:result=>{
		chiPai=result;
		count++;
		func();
	}
});
$.ajax({
	url:'/pai?eng='+eng,
	success:result=>{
		engPai=result;
		count++;
		func();
	}
});
$.ajax({
	url:'/pai?math='+math,
	success:result=>{
		mathPai=result;
		count++;
		func();
	}
});



//THEN方法中如果返回的是一个Promise实例,
// 则当前返回实例的成功或者失败状态会影响下一个THEN中哪个方法会被执行;
//如果返回的是非Promise实例,则看当前方法执行是否报错,来决定下一个THEN中哪个方法会被执行