Promise备忘

86 阅读7分钟

什么是promise

Promise是异步编程的一种解决方案,比传统的解决方案【回调函数】和【事件】更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

Promise出现的原因

队列化处理异步代码,支持链式调用,解决回调地狱

Promise的几个关键问题

1. 如何改变 promise 的状态?
  • resolve(value): 如果当前是 pending 就会变为 resolved
  • reject(reason): 如果当前是 pending 就会变为 rejected
  • 抛出异常: 如果当前是 pending 就会变为 rejected
2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗?

当 promise 改变为对应状态时都会调用,改变状态后,多个回调函数都会调用,并不会自动停止

let p = new Promise((resolve, reject) => {  resolve('OK');});
  ///指定回调 - 1
  p.then(value => {  console.log(value); });
  //指定回调 - 2
  p.then(value => { alert(value);});
3. 改变 promise 状态和指定回调函数谁先谁后?
  • 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调,但无论如何.then里面的回调都是在状态改变以后执行
  • 先改状态再指定回调(同步):改变状态 -->指定回调 并马上执行回调
  • 先指定回调再改变状态(异步):先指定回调--> 再改变状态 -->改变状态后才进入异步队列执行回调函数
  • 如何先改状态再指定回调? -->注意:指定并不是执行
  • 在执行器中直接调用 resolve()/reject() -->即,不使用定时器等方法,执行器内直接同步操作
  • 延迟更长时间才调用 then() -->即,在.then()这个方法外再包一层例如延时器这种方法
  • 什么时候才能得到数据?
  • 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据
  • 如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
let p = new Promise((resolve, reject) => {
//异步写法,这样写会先指定回调,再改变状态
setTimeout(() => {resolve('OK'); }, 1000);
//这是同步写法,这样写会先改变状态,再指定回调
resolve('OK'); 
});
p.then(value => {console.log(value);}, reason => {})
4. promise.then()返回的新 promise 的结果状态由什么决定?
  • 简单表达: 由 then()指定的回调函数执行的结果决定
  • 详细表达:
  • 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
  • 如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值
  • 如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
console.log(value);
// 1. 抛出错误 ,变为 rejected
throw '出了问题';
// 2. 返回结果是非 Promise 类型的对象,返回成功的状态和对应结果,有return返回值结果就为该返回值,没有结果就是undefined
return 521;
// 3. 返回结果是 Promise 对象,此 promise 的结果和状态就会成为result的结果和状态
return new Promise((resolve, reject) => {
  // resolve('success');
  reject('error');
});
}, reason => {
console.warn(reason);
});
5. promise链式调用?
  • promise 的.then()返回一个新的promise对象, 可以开启.then()的链式调用
  • 通过 .then() 的链式调用串连多个同步/异步任务,这样就能用then()将多个同步或异步操作串联成一个同步队列
let p = new Promise((resolve, reject) => { setTimeout(() => {resolve('OK'); }, 1000); });
p.then(value => {return new Promise((resolve, reject) => { resolve("success"); });})
.then(value => {console.log(value);})//success
.then(value => { console.log(value);})//undefined
6. promise 异常传透

当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调 前面任何操作出了异常, 都会传到最后失败的回调中处理

let p = new Promise((resolve, reject) => { 
 reject('err') 
 });
p.then(value => {console.log(value);})
.then(value => { console.log(value);})
.catch(err=>{
  console.log('错误捕获: ', err);//直接穿过前面的.then(),执行.catch()
})
7. 中断 promise 链?

当promise状态改变时,他的链式调用都会生效,那如果我们有这个一个实际需求:我们有5个then(),但其中有条件判断,如当我符合或者不符合第三个then条件时,要直接中断链式调用,不再走下面的then,该如何?

  • 当使用 promise 的 .then() 链式调用时, 在中间中断, 则不再调用后面的回调函数
  • 办法: 在回调函数中返回一个 pendding 状态的promise 对象
let p = new Promise((resolve, reject) => {setTimeout(() => { resolve('OK');}, 1000);});
p.then(value => {
  return new Promise(() => {});//中断,有且只有这一个方式
})
.then(value => { console.log(222);})
.then(value => { console.log(333);})
.catch(reason => {console.warn(reason);});

Promise中的常用 API 概述

1. Promise 构造函数: Promise (excutor) {}

executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行,换话说Promise支持同步也支持异步操作 >- executor 函数(立即执行): 执行器 (resolve, reject) =>{} >- resolve 函数: 内部定义成功时我们调用的函数 value => {} >- reject 函数: 内部定义失败时我们调用的函数 reason => {}

2. Promise.prototype.then 方法: (onResolved, onRejected) => {}
  • onResolved 函数: 成功的回调函数 (value) => {}
  • onRejected 函数: 失败的回调函数 (reason) => {}

说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调 返回一个新的 promise 对象

3. Promise.prototype.catch 方法: (onRejected) => {}
  • onRejected 函数: 失败的回调函数 (reason) => {} 说明: .then()的语法糖, 相当于: .then(undefined, onRejected)
  • 异常穿透使用:当运行到最后,没被处理的所有异常错误都会进入这个方法的回调函数中
4. Promise.resolve 方法: (value) => {}

(1) value: 成功的数据或 promise 对象

说明: 返回一个成功/失败的 promise 对象,直接改变promise状态

//  如果传入的参数是非promise对象,则返回成功的promise对象
let p3 = Promise.resolve('111')
//  如果传入的参数是promise对象,则返回的结果决定resolve的结果
 let p3 = Promise.resolve(new Promise((resolve, reject)=>{ 
   //  resolve('OK');//p3为成功
    reject('Error')//p3为失败
 })); 
 // console.log(p3)
 p3.catch(err=>console.log(err);)     
 
5. Promise.reject 方法: (reason) => {}
  • reason: 失败的原因 说明: 返回一个失败的 promise 对象,直接改变promise状态,代码示例同上
//始终返回失败的状态,失败的结果为传入的那个值,如下new Promise((resolve, reject) => {  resolve('OK'); })
  let p3 = Promise.reject(new Promise((resolve, reject) => {  resolve('OK'); }));      
6. Promise.all 方法: (promises) => {}

promises: 包含 n 个 promise 的数组 说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就直接返回失败状态并且结果为该失败promise结果

let p1 = new Promise((resolve, reject) => { resolve('成功');  })
let p2 = Promise.reject('错误错误错误');
let p3 = Promise.resolve('也是成功')
const result = Promise.all([p1, p2, p3]);
console.log(result); //状态失败,结果'错误错误错误'
7. Promise.race 方法: (promises) => {}
  • promises: 包含 n 个 promise 的数组

  • 说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终返回的结果状态,

  • 如p1延时,开启了异步,内部正常是同步进行,所以p2>p3>p1,结果是P2

 let p1 = new Promise((resolve, reject) => {
     setTimeout(() => {
       resolve('OK');
     }, 1000);
   })
   let p2 = Promise.resolve('Success');
   let p3 = Promise.resolve('Oh Yeah');
   //调用
   const result = Promise.race([p1, p2, p3]);
   console.log(result);//返回p2的的状态和结果
8. Promise.allSettled 方法:
  • Promise.allSettled() 可以获取数组中每个 promise 的结果,无论成功或失败
const runAllSettled = async () => {
    const successPromise = Promise.resolve('success') // 一个正常返回的 Promise
    const failPromise = Promise.reject('fail') // 一个异常返回的 Promise
    // 使用 allSettled
    const settiled = await Promise.allSettled([successPromise, failPromise, undefined, null])
    console.log(settiled)
    /*  输出结果如下
            [
                {status: 'fulfilled', value: 'success'},
                {status: 'rejected', reason: 'fail'},
                {status: 'fulfilled', value: undefined},
                {status: 'fulfilled', value: null},
            ]
    */
}
runAllSettled()
  • 在不支持 Promise.allSettled 新特性的环境下实现一个 Polyfill
// 通过 Promise.all 实现 Promise.allSettled
if (!Promise.allSettled) {
	Promise.allSettled = function (promises) {
		return Promise.all(
			promises.map((p) =>
				Promise.resolve(p).then(
					(value) => ({
						status: "fulfilled",
						value,
					}),
					(reason) => ({
						status: "rejected",
						reason,
					})
				)
			)
		);
	};
}

Promise的常规写法基础案例:

  1. 加载文件
// promise参数为函数类型
// 参数resolve, reject同样也得函数类型
const fs = require('fs')
const getFile=(path)=>{
  // excutor函数,同步调用
 return new Promise((resolve,reject)=>{
   fs.readFile(path, (err, data) => {
     if (err) reject(err);
     resolve(data)
   })
 })
}
getFile('./1.txt').then((res)=>{
console.log(res.toString(),'res');
}).catch((err)=>{
console.log(err,'err');
})

// 同上,调用node的util.promisify方法简写
const fs = require('fs')
const util=require('util')
const getFile1=util.promisify(fs.readFile)
getFile1('./1.txt').then((res)=>{
 console.log(res.toString(),'res');
 }).catch((err)=>{
 console.log(err,'err');
 })
  1. 举个异步加载图片的栗子
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();

image.onload = function() {
console.log('图片加载成功')
resolve(image);
};

image.onerror = function() {
reject(new Error(`无法从 ${url} 中加载图片` ));
};
image.src = url;
});
}
loadImageAsync('正确的url') //打印图片加载成功
loadImageAsync('错误的url') //抛出异常