Promise

准备

函数对象与实例对象

1.函数对象: 将函数作为对象使用时, 简称为函数对象。

2.实例对象: new 构造函数或类产生的对象, 我们称之为实例对象。

//函数对象
function Person (name,age){
	this.name = name
	this.age = age
}
Person.a = 1 //将Person看成一个对象 这是给它添加一个a属性并且赋值为1

//实例对象 --p1是Person的实例对象
const p1 = new Person('老刘',18) 
console.log(p1);

注意:每一个函数对象有一个不可修改的属性叫name

Person.name = 'tom' //这是错误的  因为name值是函数的名(Person)

回调函数的分类

什么是回调?

​ —①我们定义的函数,②我们没有调用,③最终函数执行了。

两种回调函数

同步的回调函数:

​ 理解: 立即在主线程上执行, 不会放入回调队列中。

​ 例子: 数组遍历相关的回调函数 / Promise的executor函数

异步的回调函数:

​ 理解: 不会立即执行, 会放入回调队列中以后执行(等主线程忙完)

​ 例子: 定时器回调 / ajax(成功、失败时)的回调

//演示同步的回调函数
	let arr = [1,3,5,7,9]
	arr.forEach((item)=>{
		console.log(item);
	})
	console.log('主线程的代码');

//演示异步的回调函数
	setTimeout(()=>{
		console.log('@');
	},2000)
	console.log('主线程'); 

错误类型的说明

js中错误类型有详细的划分,这里进一步理解 JS 中的错误 (Error) 和错误处理

mdn文档: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error

错误的类型

Error: 所有错误的父类型 (当发生错误时,将显示下方的子错误类型)

ReferenceError: 引用的变量不存在

TypeError: 数据类型不正确

RangeError: 数据值不在其所允许的范围内–死循环

SyntaxError: 语法错误

//演示:ReferenceError: 引用的变量不存在
	 console.log(a);

//演示:TypeError: 数据类型不正确 
//(声明一个demo常量,并且赋值一个函数,demo函数是能够被调用的,值是undefined,然后undefined再调用函数,当然显示类型不正确)
	const demo = ()=>{}
	demo()() 

//演示:RangeError: 数据值不在其所允许的范围内 
//(声明一个变量demo并赋值一个函数给他,同时函数内部再调用自己(递归函数),然后执行demo函数,然后无限的执行就会超出栈范围)
	const demo = ()=>{demo()}
	demo() 

//演示:SyntaxError: 语法错误
	 console.log(1; 

js中的错误处理

​ 捕获错误: try{}catch(){}

​ 抛出错误: throw error

如何捕获一个错误?

  • try中放可能出现错误的代码,一旦出现错误立即停止try中代码的执行,调用catch,并携带错误信息
try {
	console.log(1);
	console.log(a); //js引擎捕获到错误
	console.log(2);
} catch (error) {
	console.log('代码执行出错了,错误的原因是:',error);
} 

js中的错误处理_抛出错误

//如何抛出一个错误
function demo(){
	const date = Date.now()
	if(date % 2 === 0){
		console.log('偶数,可以正常工作');
	}else{
		throw new Error('奇数,不可以工作!')
	}
}
try {
	demo()
} catch (error) {
	debugger;
	console.log('@',error);
}

错误对象

message属性: 错误相关信息

stack属性: 记录信息

//演示:ReferenceError: 引用的变量不存在
	 console.log(a);

//演示:TypeError: 数据类型不正确
	const demo = ()=>{}
	demo()() 

//演示:RangeError: 数据值不在其所允许的范围内
	const demo = ()=>{demo()}
	demo() 

//演示:SyntaxError: 语法错误
	 console.log(1; 

Promise的理解和使用

Promise是什么?

抽象表达:

​ (1).Promise是一门新的技术(ES6提出的)

​ (2).Promise是 JS 中异步编程的新方案 ** (旧方案是谁? –**纯回调:只靠回调函数解决(ajax))

具体表达:

​ (1).从语法上来说: Promise是一个内置构造函数

​ (2).从功能上来说: Promise的实例对象可以用来 封装一个异步操作,并可以获取其成功/失败的值

总结:

  • Promise不是回调函数,是一个内置的构造函数,是程序员自己new调用的。

  • new Promise的时候,要传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数 –该函数将在构造这个新Promise对象过程中,被构造函数执行

  • 每一个Promise实例都有3种状态:初始化(pending)、成功(fulfilled)、失败(rejected)

  • 每一个Promise实例在刚被new出来的那一刻,状态都是初始化(pending)

  • executor函数会接收到2个参数,它们都是函数,分别用形参:resolve、reject接收

​ 1.调用resolve函数会:

​ (1).让Promise实例状态变为成功(fulfilled)

​ (2).可以指定成功的value –resolve(‘ok’)

​ 2.调用reject函数会:

​ (1).让Promise实例状态变为失败(rejected)

​ (2).可以指定失败的reason –reject(‘失败的原因’)

<script type="text/javascript">
	//创建一个Promise实例对象
	const p = new Promise((resolve,reject)=>{
		// resolve('ok')
		reject('失败的原因')
		// console.log(resolve);
		console.log(reject);
	})
	console.log('@',p); //一般不把Promise实例做控制台输出  @ Promise {<rejected>: 'ok'}
</script>

Promise的基本使用

重要语法

​ new Promise(executor) 构造函数

​ Promise.prototype.then 方法

基本编码流程

​ 1.创建Promise的实例对象(pending状态), 传入executor函数

​ 2.在executor中启动异步任务(定时器、ajax请求)

​ 3.根据异步任务的结果,做不同处理:

​ 3.1 如果异步任务成功了:

​ 我们调用resolve(value), 让Promise实例对象状态变为成功(fulfilled),同时指定成功的value

​ 3.2 如果异步任务失败了:

​ 我们调用reject(reason), 让Promise实例对象状态变为失败(rejected),同时指定失败的reason

​ 4.通过then方法为Promise的实例指定成功、失败的回调函数,来获取成功的value、失败的reason

​ 注意:then方法所指定的:成功的回调、失败的回调,都是异步的回调。

关于状态的注意点:

​ 1.三个状态:

​ pending: 未确定的——初始状态

​ fulfilled: 成功的——调用resolve()后的状态

​ rejected: 失败的——-调用reject()后的状态

​ 2.两种状态改变

​ pending ==> fulfilled

​ pending ==> rejected

​ 3.状态只能改变一次!!

​ 4.一个promise指定多个成功/失败回调函数, 都会调用吗?

<script>
		// 1.声明变量p,将Promise实例对象赋值给它,并且里边传入一个执行器函数executor --里边有两个参数resolve、reject
		const p = new Promise((resolve,reject)=>{
            // 2.函数体
			setTimeout(() => {				
				resolve('我是服务器返回的数据')
				reject('我是一些错误信息')
			}, 2000);
		}).then(
            // 3.成功和失败后的回调
			(value)=>{console.log('成功了',value);}, //成功的回调-异步
			(reason)=>{console.log('失败了',reason);}  //失败的回调-异步
		)
		console.log('@');
</script>

Promise与ajax配合使用

<!-- ajax配合使用 -->
<script>
		const p = new Promise((resolve,reject)=>{
            // 真正开启一个异步任务
			const xhr = new XMLHttpRequest()
			xhr.onreadystatechange = ()=>{
				if(xhr.readyState === 4) {
                    //readyState为4代表接收完毕,接收的可能是:服务器返回的成功数据、服务器返回的错误
					if (xhr.status >= 200 && xhr.status < 300){
						resolve(xhr.response)
					}else {
						reject('请求出错')
					}
				}
			}
			xhr.open('GET','https://api.apiopen.top/api/sentences')
            // !!将服务器数据转为json格式
			xhr.responseType = 'json'
			xhr.send()
		})
		p.then(
			(value)=>{console.log('成功了1',value);}, //成功的回调-异步
			(reason)=>{console.log('失败了1',reason);}  //失败的回调-异步
		)
		console.log('@');
</script>

Promise封装ajax请求

需求:

每次请求都要输入很多数据比较麻烦因此定义一个 sendAjax() 函数,对xhr的 GET 请求进行封装:

1.该函数接收两个参数:url(请求地址)、data(参数对象)

2.该函数返回一个Promise实例

​ (1).若ajax请求成功,则Promise实例成功,成功的value是返回的数据。

​ (2).若ajax请求失败,则Promise实例失败,失败的reason是错误提示。

正常使用promise发送一个ajax请求:

// 1.声明变量p,将Promise实例对象赋值给它,并且里边传入一个执行器函数executor --里边有两个参数resolve、reject
const p = new Promise((resolve, reject) => {
    // 2.实例化 xhr
	const xhr = new XMLHttpRequest()
    // 3.绑定监听
	xhr.onreadystatechange = () => {
		if (xhr.readyState === 4) {
			if (xhr.status >= 200 && xhr.status < 300) resolve(xhr.response)
			else reject('请求出错')
		}
	}
	xhr.open('GET', 'https://api.apiopen.top/api/getImages?page=0&size=10')
	xhr.responseType = 'json'
	xhr.send()
})
p.then(
	(value) => { console.log('成功了1', value); }, //成功的回调-异步
	(reason) => { console.log('失败了1', reason); }  //失败的回调-异步
)
console.log('@');

使用函数封装后:

// 1.定义函数,并准备俩参数接收地址和参数(data是参数对象)
function sendAjax(url, data) {
	// 2.准备一个promise实例 
	const p = new Promise((resolve,reject) => {
		// 3.实例化 xhr
		const xhr = new XMLHttpRequest()
		// 4.绑定监听
		xhr.onreadystatechange = () => {
			if (xhr.readyState === 4) {
				if (xhr.status >= 200 && xhr.status < 300) {
					resolve(xhr.response);
				} else {
					reject('请求出了问题');
				}
			}
		}
		// 5.整理参数 --需将 page=0&size=10 这种形式转化为 {page:0,size:10}这种形式才能放进地址中作为参数
		let str = ''
		for (let key in data) {
			str += `${key}=${data[key]}&`
		}
		str = str.slice(0, -1)
		xhr.open('GET', url + '?' + str)
		xhr.responseType = 'json'
		xhr.send(
	})
	return p 

const x = sendAjax('https://api.apiopen.top/api/getImages',{page:0,size:10})
x.then(
	(data)=>{console.log('成功了',data);},
	(reason)=>{console.log('失败了',reason);}
)

封装ajax请求(纯回调方式)

需求:

定义一个sendAjax函数,对xhr的get请求进行封装

该函数接收4个参数:url(请求地址)、data(参数对象)、success(成功的回调)、error(失败的回调)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>封装一个简单的ajax(纯回调)</title>
</head>
<body>
<script type="text/javascript">
	function sendAjax(url,data,success,error){
		//实例xhr
		const xhr = new XMLHttpRequest()
		//绑定监听
		xhr.onreadystatechange = ()=>{
			if(xhr.readyState === 4){
				if(xhr.status >= 200 && xhr.status < 300) success(xhr.response);
				else error('请求出了点问题');
			}
		}
		//整理参数
		let str = ''
		for (let key in data){
			str += `${key}=${data[key]}&`
		}
		str = str.slice(0,-1)
		xhr.open('GET',url+'?'+str)
		xhr.responseType = 'json'
		xhr.send()
	}
	sendAjax(
		'https://api.apiopen.top/api/getImages',
		{page:0,size:10},
		(response) =>{
			console.log('成功了',response);
		}, 
		(err) =>{console.log('第1次失败了',err);}
	)
</script>
</body>
</html>

Promise的API

Promise构造函数:

new Promise (executor) {}

executor函数: 是同步执行的,(resolve, reject) => {}
resolve函数: 调用resolve将Promise实例内部状态改为成功(fulfilled)。
reject函数: 调用reject将Promise实例内部状态改为失败(rejected)。
说明: **excutor函数会在Promise内部立即同步调用,**异步代码放在excutor函数中。

Promise.prototype.then方法:

Promise实例.then(onFulfilled,onRejected)
onFulfilled: 成功的回调函数 (value) => {}
onRejected: 失败的回调函数 (reason) => {}
特别注意(难点):then方法会返回一个新的Promise实例对象

验证then的返回值

//验证:then方法会返回一个新的Promise实例对象。(暂且不研究返回的这个新Promise实例状态怎么变化)
const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve(100)
	},1000)
})
const x = p.then(
   value => {console.log('成功了',value);},
   reason =>{console.log('失败了',reason);}
)
console.log('50'); 
console.log(x);   // pomise {<pending>}

Promise.prototype.catch方法:

Promise实例.catch(onRejected)
onRejected: 失败的回调函数 (reason) => {}
说明: catch方法是then方法的语法糖, 相当于: then(undefined, onRejected)

//Promise.prototype.catch方法
const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject(100)
	},1000)
})
p.then(
	value => {console.log('成功了1',reason);},
	reason => {console.log('失败了1',reason);}
)
p.catch(
	reason => {console.log('失败了2',reason);}
) 

Promise.resolve方法:

Promise.resolve(value)
说明: 用于快速返回一个状态为fulfilled或rejected的Promise实例对象
备注:当状态为rejected是因为value的值可能是:

(1)非Promise值 – 100、’abc’、{}、[]

(2)Promise值 – Promise实例(此时值是传入的promise实例,值成功或者失败)

例子:Promise.resolve方法生成了一个状态为失败的Promise对象实例 p

//Promise.resolve
const p0 = Promise.reject(-100)
const p = Promise.resolve(p0)

p.then(
	value => {console.log('成功了',value);},	
    reason => {console.log('失败了',reason);}
) 

Promise.reject方法:

Promise.reject方法(reason)
说明: 用于快速返回一个状态必为rejected的Promise实例对象

例子:Promise.reject方法即使传入一个状态为成功的Promise对象实例值也是失败的 即失败的原因是那个成功的promise对象实例

//Promise.reject
const p0 = Promise.resolve(100)
const p = Promise.reject(p0)
p.then(
	value => {console.log('成功了',value);},
	reason => {console.log('失败了',reason);}
)  

Promise.all方法:

Promise.all(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败。

//Promise.all
const p1 = Promise.resolve('a')
const p2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject('b')     //有一个失败了就直接失败。
	},500)
})
const p3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject('c')
	},2000)
})
const x = Promise.all([p1,p2,p3])
x.then(
	value => {console.log('成功了',value);},
	reason => {console.log('失败了',reason);}
)

Promise.race方法:

Promise.race(promiseArr)
promiseArr: 包含n个Promise实例的数组
说明: 返回一个新的Promise实例, 成功还是很失败?以最先出结果的promise为准。

//Promise.race
const p1 = Promise.reject('a')
const p2 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('b')
	},500)
})
const p3 = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject('c')
	},2000)
})
const x = Promise.race([p3,p1,p2])
x.then(
	value => {console.log('成功了',value);},		
    reason => {console.log('失败了',reason);}
)

Promise的几个关键问题:

如何改变Promise实例的状态?

  • 执行resolve(value): 如果当前是pending就会变为fulfilled

  • 执行reject(reason): 如果当前是pending就会变为rejected

  • 执行器函数(executor)抛出异常: 如果当前是pending就会变为rejected

<script type="text/javascript" >
	const p  = new Promise((resolve,reject)=>{
		console.log(a); 	//浏览器引擎抛异常
		// throw 900 		//编码抛异常
	})
	p.then(
		value => {console.log('成功了',value);},
		reason => {console.log('失败了',reason);}
	)
</script>

改变Promise实例的状态和指定回调函数谁先谁后?

  • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
//先指定回调,后改变状态(最常见)
const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('a')
	},4000)
})
p.then(
	value => {console.log('成功了',value);},
	reason => {console.log('失败了',reason);}
) 

如何先改状态再指定回调?

  • 延迟一会再调用then()
//先改状态,后指定回调
const p = new Promise((resolve,reject)=>{
	resolve(100)
})
setTimeout(()=>{
	p.then(
		value => {console.log('成功了',value);},
		reason => {console.log('失败了',reason);}
	)
},2000)

Promise实例什么时候才能得到数据?

  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据

then如何链式调用?

Promise实例.then()返回的是一个【新的Promise实例】

它的值和状态由什么决定?

简单表达: 由then()所指定的回调函数执行的结果决定

详细表达:

  • 如果then所指定的回调返回的是非Promise值a:

​ 那么【新Promise实例】状态为:成功(fulfilled), 成功的value为a

  • 如果then所指定的回调返回的是一个Promise实例p:

​ 那么【新Promise实例】的状态、值,都与p一致

  • 如果then所指定的回调抛出异常:

​ 那么【新Promise实例】状态为rejected, reason为抛出的那个异常

const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('a')
	},1000)
})
p.then(
	value => {console.log('成功了1',value); return Promise.reject('a')},
	reason => {console.log('失败了1',reason);}
).then(
	value => {console.log('成功了2',value);return true},
	reason => {console.log('失败了2',reason); return 100}
).then(
	value => {console.log('成功了3',value);throw 900},
	reason => {console.log('失败了3',reason); return false}
).then(
	value => {console.log('成功了4',value);return -100},
	reason => {console.log('失败了4',reason);}
)

纯回调会引起的问题?

如下:可以发现代码非常的混乱,不利于查阅,且当再多发几次的请求后更加难以查阅

 // 调用之前封装好的 sendAjax函数
sendAjax(
	'https://api.apiopen.top/api/getImages',
	{page:0,size:10},
	(response) =>{
	//成功的回调1
	console.log('第一次成功了',response)
		sendAjax(
			'https://api.apiopen.top/api/getImages',
			{page:0,size:10},
			(response) =>{
				//成功的回调2
				console.log('第二次成功了' ,response)
				sendAjax(
						'https://api.apiopen.top/api/getImages',
						{page:0,size:10},
						(response) =>{
							//成功的回调3
							console.log('第三次成功了',response)
						}, 
						(err) =>{
							// 失败的回调3
							console.log('第三次失败了',err);
						}
				)
			}, 
			(err) =>{
				// 失败的回调2
				console.log('第二次失败了',err);
			}
		)
	}, 
	(err) =>{
		// 失败的回调1
		console.log('第1次失败了',err);
	}
)

使用then的链式调用解决回调地狱

注意:then方法会返回一个新的Promise实例对象 但是因为其成功的value回调里还返回了 sendAjax()方法,该方法的返回值也是Promise实例对象,所以then返回的新Promise实例的状态、值,都与sendAjax()方法返回的promise一致 (详情见上方 ‘then如何链式调用‘ )

// 调用之前封装好的 sendAjax函数 (详情见上方 'Promise封装ajax请求')
//发送第1次请求
sendAjax('https://api.apiopen.top/api/getImages',{page:0})
/* 
注意:then方法会返回一个新的Promise实例对象 但是因为其成功
的value回调里还返回了 sendAjax()方法,该方法的返回值也是
Promise实例对象,所以then返回的新Promise实例的状态、值,
都与sendAjax()方法返回的promise一致 (详情见上方 'then如何链式调用' )
*/
.then( 
	value => {
		console.log('第1次请求成功了',value);
		//发送第2次请求 	
		return sendAjax('https://api.apiopen.top/api/getImages',{page:0}) 
	},
	reason => {console.log('第1次请求失败了',reason);}
)
.then(
	value => {
		console.log('第2次请求成功了',value);
		//发送第3次请求
		return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
	},
	reason => {console.log('第2次请求失败了',reason);}
)
.then(
	value => {console.log('第3次请求成功了',value);},
	reason => {console.log('第3次请求失败了',reason);}
)

中断Promise链

原因:

代码同上的时候,假如将发送第1次请求的地址写错后,第二次、第三次依然会因为种种原因继续执行。

思路:

当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数。

办法:

在失败的回调函数中返回一个pendding状态的Promise实例。在每次reason后加上 ‘ return new Promise(()=>{}) ’ 这样then返回的promise实例对象状态是pending就不会执行后边的value和reason

代码如下:

.then( 
	value => {
		console.log('第1次请求成功了',value);
		//发送第2次请求 	
		return sendAjax('https://api.apiopen.top/api/getImages',{page:0}) 
	},
	reason => {console.log('第1次请求失败了',reason);return new Promise(()=>{})}
)
.then(
	value => {
		console.log('第2次请求成功了',value);
		//发送第3次请求
		return sendAjax('https://api.apiopen.top/api/getImages',{page:0})
	},
	reason => {console.log('第2次请求失败了',reason);return new Promise(()=>{})}
)
.then(
	value => {console.log('第3次请求成功了',value);},
	reason => {console.log('第3次请求失败了',reason);}
)

Promise的错误穿透

  • 当使用promise的then链式调用时, 可以在最后用catch指定一个失败的回调,

  • 前面任何操作出了错误, 都会传到最后失败的回调中处理了

备注:如果不存在then的链式调用,就不需要考虑then的错误穿透。

原理:底层帮我们在then里补上了一个失败的回调 reason => {throw reason}

又因为then所指定的回调抛出异常,那么新Promise实例状态为rejected, reason为抛出的那个异常

//另一个例子演示错误的穿透
const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		reject(-100)
	},1000)
})
p.then(
	value => {console.log('成功了1',value);return 'b'},
	reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.then(
	value => {console.log('成功了2',value);return Promise.reject(-108)},
	reason => {throw reason}//底层帮我们补上的这个失败的回调
)
.catch(
	// reason => {throw reason}
	reason => {console.log('失败了',reason)}
)

Promise的优势

优势:

  • 指定回调函数的方式更加灵活:

​ 旧的: 必须在启动异步任务前指定(如之前用jQuery封装的get请求,需要提前写好成功和失败的回调)

​ promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)

  • 支持链式调用, 可以解决回调地狱问题

​ (1)什么是回调地狱:

​ 回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件

​ (2)回调地狱的弊病:

​ 代码不便于阅读、不便于异常的处理

​ (3)一个不是很优秀的解决方案:

​ then的链式调用

​ (4)终极解决方案:

​ async/await(底层实际上依然使用then的链式调用)

async/await的使用

  • 注意:

​ await必须写在async函数中, 但async函数中可以没有await

​ 如果await的Promise实例对象失败了, 就会抛出异常, 需要通过try…catch来捕获处理


const p1 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('a')
	}, 1000)
})
const p2 = new Promise((resolve, reject) => {
	setTimeout(() => {
		reject('b')
	}, 2000)
})
const p3 = new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('c')
	}, 4000)
})

// async function demo() {
/*当Promise实例对象p状态为失败时,
一旦try中的代码发生了抛异常,
将会携带异常来到catch,此时err就是抛出的'a'
*/

//(记得前边加分号)
; (async () => {
	// try中放可能存在错误的代码
	try {
		const result1 = await p1
		console.log('成功了', result1);
		const result2 = await p2
		console.log('成功了', result2);
		const result3 = await p3
		console.log('成功了', result3);
	} catch (error) {
		console.log('失败了',error);
	}
})()

await的应用(解决链式调用)

//准备好一个 Promise封装好的发送ajax get请求的函数 --sendAjax(url,data)
(async()=>{
	try {
        // 如果wait右侧的表达式为Promise实例对象,await后的返回值是promise成功的值
		const result1 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
		console.log('第1次请求成功了',result1);
		const result2 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
		console.log('第2次请求成功了',result2);
		const result3 = await sendAjax('https://api.apiopen.top/api/getImages',{page:0})
		console.log('第3次请求成功了',result3);
	} catch (error) {
			console.log(error);
	}
})()

async与await的规则

  • async修饰的函数

​ 函数的返回值为promise对象

​ Promise实例的结果由async函数执行的返回值决定

  • await表达式

​ await右侧的表达式一般为Promise实例对象, 但也可以是其它的值

​ (1).如果表达式是Promise实例对象, await后的返回值是promise成功的值

​ (2).如果表达式是其它值, 直接将此值作为await的返回值

//测试async
async function demo(){
	const result = await p1
	console.log(result);
}
demo()

await原理

若我们使用async配合await这种写法:

1.表面上不出现任何的回调函数

2.但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了。

3.最终运行的代码是依然有回调的,只是程序员没有看见。

const p = new Promise((resolve,reject)=>{
	setTimeout(()=>{
		resolve('a')
	},4000)
})
async function demo(){
	//程序员“轻松”的写法 
	const result = await p
	console.log(result);
	console.log(100);
	console.log(200);		
}
demo()
console.log(1);
//浏览器翻译后的代码
/* 
	p.then(
		result => {
			console.log(result);
			console.log(100);
			console.log(200);
		},
	) 
*/

宏队列与微队列

宏队列:[宏任务1,宏任务2…..] –如主线程上定时器就是宏任务

微队列:[微任务1,微任务2…..] –promise实例对象的then方法内就是微任务

规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务

1.如果有,先执行微任务

2.如果没有,按照宏队列里任务的顺序,依次执行

//代码一
setTimeout(()=>{
       console.log('timeout')
},0)

Promise.resolve(1).then(
       value => console.log('成功1',value)
)
Promise.resolve(2).then(
       value => console.log('成功2',value)
)
console.log('主线程')
      
// 控制台结果
主线程
成功1 1
成功2 2
timeout
//代码二
setTimeout(()=>{
       console.log('timeout1')
})
setTimeout(()=>{
       console.log('timeout2')
})

Promise.resolve(1).then(
       value => console.log('成功1',value)
)
Promise.resolve(2).then(
       value => console.log('失败2',value)
)
      
// 控制台结果
成功1 1
失败2 2
timeout1
timeout2
//代码三
setTimeout(()=>{
  console.log('timeout1')
  Promise.resolve(5).then(
    value => console.log('成功了5')
  )
})
setTimeout(()=>{
  console.log('timeout2')
})
Promise.resolve(3).then(
  value => console.log('成功了3')
)
Promise.resolve(4).then(
  value => console.log('成功了4')
) 

// 控制台结果
成功了3
成功了4
timeout1
成功了5
timeout2

经典面试题

典型的先指定回调后改变状态

以下代码的执行流程:
首先可以判断是先指定回调再改变状态
主线程上的代码在飞速运行,new Promise后瞬间开始执行代码。然后执行器函数excuter是同步的回调函数,由于同步的回调函数不会往回调队列里边进,主线程上直接执行这些代码,瞬间开启一个定时器,那么setTimeout所指定的回调不是马上推入队列(除非到点了),而是放到浏览器的定时器管理模块,到点后定时器管理模块把setTimeout所指定的回调再推向宏队列,当主线程任务完成后才执行setTimeout所指定的回调。
再来看主线程,new Promise在主线程上使用后,执行器函数也是在主线程上执行,定时器setTimeout内置函数也是在主线程上飞速的执行完毕(而最重要的是setTimeout其里边的函数体需要到点后再推向队列),然后主线程开始执行p.then(),这其中有成功和失败的两个函数参数,是挂在实例自身上了。可以理解为只要是Promise实例对象,那么一出生身上就有个类似 P.list = []的结构,于是这里把成功和失败的回调推向这个里边(不是推向队列,因为还不知到上方状态的结果,除非是知道状态才去调用成功/失败的回调)一旦setTimeout内函数体从宏队列拉出执行,才发现状态是否成功。然后去自身找到之前缓存的成功/失败的回调。

const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
    // 函数体
    resolve('ok')
    },1000)
})
p.then(
   (value)=>{console.log(value);},
   (reason)=>{console.log(reason);}
)

典型的先改状态后指定回调

/* 典型的先改状态后指定回调
这个Promise实例对象一出生瞬间变为成功的状态,然后指定回调。即成功后直接把成功的回调推向了微队列。
(记住要执行的时候才进队列,等着主线程将它勾出来去执行)
*/
const p = new Promise((resolve,reject)=>{
    resolve('ok')
})
p.then(
   (value)=>{console.log(value);},
   (reason)=>{console.log(reason);}
)

面试题

判断出控制台的输出结果?

注意:分析方法的注释是按照1~9顺序排列

	// 1、立马反应setTimeout立即调用,但是不能进主线程而是进宏队列  宏队列保存 0
setTimeout(()=>{
    console.log('0');
},0)
	// 2、当主线程执行到此时,控制台输出 1,并且new一个Promise 判断出它是一个同步的回调 立刻马上输出1 且瞬间状态切换为成功 
new Promise((resolve,reject)=> {
    console.log('1');
    resolve()
	// 3、状态成功后执行 .then()方法里的回调,但是!!这是属于异步回调,需要先推入微队列  微队列保存 2
}).then(()=>{
    /* 
       6、主线程分析完后,接着开始分析微队列
       此时将2输出到控制台,此刻控制台为 1、7、2  此时微队列抹去 2 
       又 new了一个Promise,执行器在主线程运行马上输出3,同时resolve()将状态变为成功了 此刻控制台为 		1、7、2、3
       状态成功后,在.then()方法里有一个为其指定得成功得回调 ,!!注意这个回调也先得推入微队列,即把4所在		回调推入微队列.此时队列保存8、4
       下一个.then()也是为左侧.then()返回得新Promise实例对象指定得回调,但是前一个实例的状态是由4所在		的回调来定义,而4目前还未执行,所以5就挂在了自身上不推入队列
       此刻当前花括号内代码执行完毕,程序默认给Promise返回的值为undefined
    */
    console.log('2');
    new Promise((resolve,reject)=> {
        console.log('3');
        resolve()
    }).then(()=>{
    /* 
       9、接着将4所在的回调拉入主线程执行 输出4  此刻控制台为 1、7、2、3、8、4
       如果4所在的回调执行了,那么就说明所在的.then的状态就敲定了,返回undefined
       因为.then()左侧有了结果,就把 5 推入微队列 ,此时微队列保存6、5
       接着将6(所在的回调)拿出队列,推入主线程,此刻控制台为 1、7、2、3、8、4、6
       接着将5(所在的回调)拿出队列,推入主线程,此刻控制台为 1、7、2、3、8、4、6、5
       最后 把宏队列的0(所在的回到)推入主线程,此刻控制台为 1、7、2、3、8、4、6、5、0
    */
        console.log('4');
    }).then(()=>{
        console.log('5');
    })
    /* 
       4、这里的.then()不会推入队列,是因为它左侧的Promise实例还没有执行没有返回出结果,这里当然不能执			行,而是暂存放到左侧Promise实例身上
    */ 
}).then(()=>{
    /*
       7、因为.then左侧返回undefined,所以新Promise实例状态为成功, 成功的value为undefined
       	  因为.then()左侧有了结果,就把 6 推入微队列 ,此时队列保存8、4、6
    */ 
    console.log('6');
})
    /* 
       5、又 new了一次Promise 此时控制台先输出 1、7 ,因为直接改变状态为成功,意味着要去调用后边			  的.then()方法,当然得先把.then()方法推向微队列 此时队列保存 2 8
     */
new Promise((resolve,reject)=>{
    console.log('7')
    resolve()
}).then(()=>{
    // 8、接着将8从队列拉出,此刻控制台为 1、7、2、3、8 此时队列保存4、6
    console.log('8');
})

包管理器的对比:

仅用cnpm的仓库地址

使用yarn的命令

偶尔yarn网络不好,使用npm