promise学习笔记

139 阅读8分钟

Promise

Promise是什么

1.抽象表达:

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

(2).Promise是JS中异步编程的新方案(旧方案是谁?---纯回调)

2.具体表达:

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

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

    1.Promise不是回调,是一个内置的构造函数,是程序员自己new调用的。
•    2.new Promise的时候,要传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数
•    3.每一个Promise实例都有3种状态:初始化(pending)、成功(fulfilled)、失败(rejected)
•    4.每一个Promise实例在刚被new出来的那一刻,状态都是初始化(pending)
•    5.executor函数会接收到2个参数,它们都是函数,分别用形参:resolve、reject接收
•          1.调用resolve函数会:
•              (1).让Promise实例状态变为成功(fulfilled)
•              (2).可以指定成功的value。
​
•          2.调用reject函数会:
•              (1).让Promise实例状态变为失败(rejected)
•              (2).可以指定失败的reason。

基本使用

重要语法

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 type="text/javascript" >const p = new Promise((resolve,reject)=>{
      //模拟一个异步任务
      /* setTimeout(()=>{
        resolve('我是成功的数据')
      },2000) *///真正开启一个异步任务
      const xhr = new XMLHttpRequest()
      xhr.onreadystatechange = ()=>{
        if(xhr.readyState === 4){
          //readyState为4代表接收完毕,接收的可能是:服务器返回的成功数据、服务器返回的错误
          if(xhr.status === 200) resolve(xhr.response)
          else reject('请求出错')
        }
      }
      xhr.open('GET','https://api.apiopen.top/getJoke')
      xhr.responseType = 'json'
      xhr.send()
    })
    
    p.then(
      (value)=>{console.log('成功了1',value);}, //成功的回调-异步
      (reason)=>{console.log('失败了1',reason);} //失败的回调-异步
    )
    console.log('@');
​
  </script>

封装ajax的GET

  /*
    定义一个sendAjax函数,对xhr的get请求进行封装:
        1.该函数接收两个参数:url(请求地址)、data(参数对象)
        2.该函数返回一个Promise实例
              (1).若ajax请求成功,则Promise实例成功,成功的value是返回的数据。
              (2).若ajax请求失败,则Promise实例失败,失败的reason是错误提示。
  */
  function sendAjax(url,data){
    return new Promise((resolve,reject)=>{
        //实例xhr
        const xhr = new XMLHttpRequest()
        //绑定监听
        xhr.onreadystatechange = ()=>{
          if(xhr.readyState === 4){
            if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
            else reject('请求出了点问题');
          }
        }
        //整理参数
        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()
      })
  }
  
  const x = sendAjax('https://api.apiopen.top/getJoke',{page:1,count:2,type:'video'})
  x.then(
    (data)=>{console.log('成功了',data);},
    (reason)=>{console.log('失败了',reason);}
  )

Promise的API

new promise()

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

静态方法

1. Promise.resolve()

   1. Promise.resolve方法: Promise.resolve(value)
      说明: 用于快速返回一个状态为fulfilled或rejected的Promise实例对象
      备注:value的值可能是:(1)非Promise值  (2)Promise
resolve传入的值:   1.Promise值,成功的状态
                  2. Promise值,状态与值都保持一致, p1,p2指向同一个promise实例
                  3. thenable对象, 调用then()方法后的状态 就是 新的Promise实例的状态
1.  
    const p2 = Promise.resolve(100)
    p2.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
   //成功了 100
2.如果它正在解决一个原生的 Promise,它将返回同一 Promise 实例
    const p1 = Promise.reject(-100)
    const p2 = Promise.resolve(p1)
    p2.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
   // 失败了 -1003.const p2 = Promise.resolve({
    then(resolve, reject){
      reject(-100)
    }
  })
  p2.then(
    (value) => { console.log('成功了', value) },
    (reason) => { console.log('失败了', reason) }
  )
// 失败了 -100

2. Promise.reject()

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

reason: 该 Promise 对象被拒绝的原因。

Promise.reject() 静态方法返回一个已拒绝(rejected)的 Promise 对象,拒绝原因为给定的参数。
1.Promise值
    const p2 = Promise.reject(-100)
    p2.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    // 失败了 -1002. Promise值: 将p1看做普通值,失败的原因就是p1
  const p1 = Promise.reject('failure')
  const p2 = Promise.reject(p1)
  p2.then(
    (value) => { console.log('成功了', value) },
    (reason) => { console.log('失败了', reason) }
  )
// 失败了 Promise {<rejected>: 'failure'}

3. Promise.all()

   1. Promise.all方法: Promise.all(promiseArr)
      promiseArr: 包含n个Promise实例的数组
      说明: 返回一个新的Promise实例, 只有所有的promise都成功才成功, 只要有一个失败了就直接失败。
Promise.all() 静态方法接受一个 Promise 可迭代对象作为输入,并返回一个 Promise。当所有输入的 Promise 都成功时,返回的 Promise 也将成功(即使传入的是一个空的可迭代对象),并返回一个包含所有成功值的数组。如果输入的任何 Promise 被拒绝,则返回的 Promise 将被拒绝,并带有第一个被拒绝的原因。
1. 所有promise都成功,才成功
		const p1 = Promise.resolve('ok')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('success')
      }, 1000)
    })

    const p = Promise.all([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    // 成功了 (3) ['ok', '成功了', 'success']
2. 返回最先失败的原因
    const p1 = Promise.resolve('ok')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('faliure')
      }, 1000)
    })

    const p = Promise.all([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    //失败了 faliure

4. Promise.allSettled()

Promise.allSettled() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个单独的 Promise。当所有输入的 Promise 都已敲定时(包括传入空的可迭代对象时),返回的 Promise 实例状态为成功,并带有描述每个 Promise 结果的对象数组。

		const p1 = Promise.resolve('ok')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('faliure')
      }, 1000)
    })

    const p = Promise.allSettled([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )

    // 成功了 (3) [{…}, {…}, {…}]
    0: {status: 'fulfilled', value: 'ok'}
    1: {status: 'fulfilled', value: '成功了'}
    2: {status: 'rejected', reason: 'faliure'}

5. Promise.race()

   1. Promise.race方法: Promise.race(promiseArr)
      promiseArr: 包含n个Promise实例的数组
      说明: 返回一个新的Promise实例, 成功还是很失败?以最先出结果的promise为准。
      
   Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。
    const p1 = Promise.resolve('ok')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('faliure')
      }, 1000)
    })

    const p = Promise.race([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    //成功了 ok

6. Promise.any()

Promise.any() 静态方法将一个 Promise 可迭代对象作为输入,并返回一个 Promise。当输入的任何一个 Promise 兑现时,这个返回的 Promise 将会兑现,并返回第一个兑现的值。当所有输入 Promise 都被拒绝(包括传递了空的可迭代对象)时,则返回一个拒绝的原因

1. 任一实例成功,则新的promise为成功,返回第一个成功的值
    const p1 = Promise.reject('no OK')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('成功了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('faliure')
      }, 1000)
    })

    const p = Promise.any([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    // 成功了 成功了

2. 所有实例都失败,返回错误信息
    const p1 = Promise.reject('no OK')
    const p2 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('失败了')
      }, 500)
    })
    const p3 = new Promise((resolve, reject) => {
      setTimeout(() => {
        reject('faliure')
      }, 1000)
    })

    const p = Promise.any([p1, p2, p3])
    p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    )
    //失败了 AggregateError: All promises were rejected

实例方法

Promise.prototype.then()

   1. Promise.prototype.then方法: Promise实例.then(onFulfilled,onRejected)
      onFulfilled: 成功的回调函数 (value) => {}
      onRejected: 失败的回调函数 (reason) => {}
      特别注意(难点):then方法会返回一个新的Promise实例对象
			const p = new Promise((reslove, reject) => { 
        setTimeout(() => {
          reslove('ok')
        }, 1000)
     	})
      p.then(
        (value) => { console.log('成功了', value) },
        (reason) => { console.log('失败了', reason) }
      )
    // 成功了 ok

Promise.prototype.catch()

   1. Promise.prototype.catch方法: Promise实例.catch(onRejected)
      onRejected: 失败的回调函数 (reason) => {}
      说明: catch方法是then方法的语法糖, 相当于: then(undefined, onRejected)
// Promise 实例的 catch() 方法用于注册一个在 promise 被拒绝时调用的函数。它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 的方法。此方法是 Promise.prototype.then(undefined, onRejected) 的一种简写形式。
		const p = new Promise((reslove, reject) => { 
      setTimeout(() => {
        reject('-100')
      }, 1000)
     })
     p.then(
      undefined,
      (reason) => { console.log('失败了', reason) }
    )
    p.catch(
      (reason) => { console.log('失败了', reason) }
    )
    // 失败了 -100
    // 失败了 -100
		// undefined 知识占位,底层做了封装, 不会报错

Promise.prototype.finally()

Promise 实例的 finally() 方法用于注册一个在 promise 敲定(兑现或拒绝)时调用的函数。

它会立即返回一个等效的 Promise 对象,这可以允许你链式调用其他 promise 方法。

表示无论Promise对象无论变成fulfilled还是rejected状态,最终都会被执行的代码

    const p = new Promise((resolve, reject) => { 
      setTimeout(() => {
        resolve('100')
      }, 1000)
     })
     p.then(
      (value) => { console.log('成功了', value) },
      (reason) => { console.log('失败了', reason) }
    ).catch(
      (reason) => { console.log('失败了', reason) }
    ).finally(() => { 
      console.log('finally code')
     })
     // 成功了 100
     // finally code

改变promise的状态

	<!-- 
			1.	如何改变一个Promise实例的状态?
						(1)执行resolve(value): 如果当前是pending就会变为fulfilled
						(2)执行reject(reason): 如果当前是pending就会变为rejected
						(3)执行器函数(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实例的状态和指定回调函数谁先谁后?
					1.都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
					2.如何先改状态再指定回调?
								延迟一会再调用then()
					3.Promise实例什么时候才能得到数据?
								如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
								如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
	-->
	<script type="text/javascript" >
		//先指定回调,后改变状态(最常见)
		//#region 
		/* const p = new Promise((resolve,reject)=>{
			setTimeout(()=>{
				resolve('a')
			},4000)
		})
		p.then(
			value => {console.log('成功了',value);},
			reason => {console.log('失败了',reason);}
		) */
		//#endregion
	
		//先改状态,后指定回调
		const p = new Promise((resolve,reject)=>{
			resolve(100)
		})
		setTimeout(()=>{
			p.then(
				value => {console.log('成功了',value);},
				reason => {console.log('失败了',reason);}
			)
		},2000)
	</script>

Then()的链式调用

<!-- 
			Promise实例的值与状态的规则:
		 	Promise实例.then()返回的是一个【新的Promise实例】,它的值和状态由什么决定?
					1.简单表达: 由当前then()所指定的回调函数执行的结果决定(return 的结果)
					2.详细表达:
							(1)如果then所指定的回调返回的是非Promise值a:
											那么【新Promise实例】状态为:成功(fulfilled), 成功的value为a
							(2)如果then所指定的回调返回的是一个Promise实例p:
											那么【新Promise实例】的状态、值,都与p一致
							(3)如果then所指定的回调抛出异常:
											那么【新Promise实例】状态为rejected, reason为抛出的那个异常
	 -->
  <script>
		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);}
		)
  </script>

中断Promise链

	<!-- 
		中断promise链:
					(1)当使用promise的then链式调用时, 在中间中断, 不再调用后面的回调函数。
					(2)办法: 在失败的回调函数中返回一个pendding状态的Promise实例。return new Promise(()=>{})
	 -->
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
  <script>
		function sendAjax(url,data){
			return new Promise((resolve,reject)=>{
				//实例xhr
				const xhr = new XMLHttpRequest()
				//绑定监听
				xhr.onreadystatechange = ()=>{
					if(xhr.readyState === 4){
						if(xhr.status >= 200 && xhr.status < 300) resolve(xhr.response);
						else reject('请求出了点问题');
					}
				}
				//整理参数
				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()
			})
		}
		//发送第1次请求
		sendAjax('https://api.apiopen.top/getJoke',{page:1})
		.then(
			value => {
				console.log('第1次请求成功了',value);
				//发送第2次请求
				return sendAjax('https://api.apiopen.top/getJoke2',{page:1})
			},
			reason => {console.log('第1次请求失败了',reason);return new Promise(()=>{})}
		)
		.then(
			value => {
				console.log('第2次请求成功了',value);
				//发送第3次请求
				return sendAjax('https://api.apiopen.top/getJoke',{page:1})
			},
			reason => {console.log('第2次请求失败了',reason);return new Promise(()=>{})}
		)
		.then(
			value => {console.log('第3次请求成功了',value);},
			reason => {console.log('第3次请求失败了',reason);}
		)
	</script>

错误穿透

<!-- 
			promise错误穿透:
					(1)当使用promise的then链式调用时, 可以在最后用catch指定一个失败的回调,
					(2)前面任何操作出了错误, 都会传到最后失败的回调中处理了
			备注:如果不存在then的链式调用,就不需要考虑then的错误穿透。
	 -->
  <script>
		//另一个例子演示错误的穿透
		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}
		)
  </script>

promise的优势

 
		优势:
		  1. 指定回调函数的方式更加灵活: 
				    旧的: 必须在启动异步任务前指定
				    promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定)

		  2. 支持链式调用, 可以解决回调地狱问题
				    (1)什么是回调地狱:
									回调函数嵌套调用, 外部回调函数异步执行的结果是嵌套的回调函数执行的条件
				    (2)回调地狱的弊病:
									代码不便于阅读、不便于异常的处理
				    (3)一个不是很优秀的解决方案:
									then的链式调用
				    (4)终极解决方案:
									async/await(底层实际上依然使用then的链式调用)
    

async和await

<!-- 
		1. async修饰的函数
					函数的返回值为promise对象
					Promise实例的结果由async函数执行的返回值决定

		2. await表达式
					await右侧的表达式一般为Promise实例对象, 但也可以是其它的值
					(1).如果表达式是Promise实例对象, await后的返回值是promise成功的值
					(2).如果表达式是其它值, 直接将此值作为await的返回值
		
		3. 注意:
				await必须写在async函数中, 但async函数中可以没有await
				如果awaitPromise实例对象失败了, 就会抛出异常, 需要通过try...catch来捕获处理
 -->
<script>
	const p1 = new Promise((resolve,reject)=>{
		setTimeout(()=>{
			resolve('a')
		},1000)
	})
	const p2 = new Promise((resolve,reject)=>{
		setTimeout(()=>{
			resolve('一些错误')
		},2000)
	})
	const p3 = new Promise((resolve,reject)=>{
		setTimeout(()=>{
			resolve('c')
		},4000)
	})


	;(async()=>{
		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原理

	<!-- 
		 若我们使用async配合await这种写法:
         1.表面上不出现任何的回调函数
         2.但实际上底层把我们写的代码进行了加工,把回调函数“还原”回来了。
         3.最终运行的代码是依然有回调的,只是程序员没有看见。
	 -->
<script>
  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);

		//浏览器翻译后的代码
		/* p.then(
			result => {
				console.log(result);
				console.log(100);
				console.log(200);
			},
		) */
		
	}
	demo()
	console.log(1);

微任务和宏任务

	/* 
			宏队列:[宏任务1,宏任务2.....]
			微队列:[微任务1,微任务2.....]
			规则:每次要执行宏队列里的一个任务之前,先看微队列里是否有待执行的微任务
						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('主线程')


      //代码二
      /* 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)
      ) */

      //代码三
       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')
      ) 

  </script>