Promise

Promise是ES6中提出的处理异步的异步编程方法

promise能够解决回调顺序的不确定性和回调地狱(回调地狱是指回调代码嵌套程度加深)

1. Promise的状态

promise是一个容器,容器内部保存了异步事件的状态/结果

  1. pending 等待态
  2. resolved 执行态
  3. rejected 拒绝态
  4. 当状态发生改变时,状态保持不变

2. Promise的状态转移方法

创建一个promise对象:默认参数是两个方法,resolve和reject

  • 当异步事件成功则调用resolve方法来传参(传递终值),并且Promise的状态由pending变为resolved
  • 若失败则调用reject方法传参(传递拒绝原因),并且Promise的状态由pending变为rejected
const p = new Promise((resolve, reject)=>{
	setTimeout(function () {
		resolve('hello promise');
	}, 2000)
});

3. Promise的then方法

一个Promise必须提供一个then方法以访问/获取当前终值和拒因,Promise的then方法接收两个参数,这两个参数实际上是监听函数

promise.then(onFulfilled, onRejected);

//上面的例子中
p.then(res=>{console.log(res)});
  1. onFulfilled:监听函数,当Promise中调用resolve便执行onFulfilled
  2. onRejected:监听函数,当Promsie中调用reject便执行onRejected

then方法必须返回一个Promise对象,根据这个属性我们可以进行链式调用

promise2 = promise1.then(onFulfuilled, onRejected);

// 虽然then方法返回一个Promise对象,但是返回的promise是不同的
// q = p.then((res)=>{console.log(res)}) q:Promise {<resolved>: undefined}
// q = p.then((res)=>{console.log(res); return 1;}) q:Promise {<resolved>: 1}

当需要发起异步请求,不用一直等待异步事件,只需要等有结果了通过then来监听并调用回调函数,并且当存在多个需要顺序执行的异步操作时,能够进行链式调用

4. catch方法

catch方法属于Promise中then方法的语法糖,等价于then(null, (err)=>{})

实际上也是一个then,catch方法与then方法第二个参数其实是一个东西

then方法中的第二个参数只能捕获前一个Promise对象返回的rejected状态

catch方法可以捕获前面某一个Promise对象返回的rejected状态

Promise.resolve("new").then((value)=>{
    Promise.resolve("other").then(()=>{
        throw new Error("error");
    })
}).catch((reason)=>{
    console.log(reason);
})

内层的promise确实是抛出了错误,但它没有return给外层promise,此时相当于return undefined,这样就不是错误,catch也就没法捕获

正确做法:

Promise.resolve("new").then((value)=>{
	// return promise
    return Promise.resolve("other").then(()=>{
        throw new Error("error");
    })
    
}).catch((reason)=>{
    console.log(reason);
})

5. 封装promise

function returnPromise(){  
	return new Promise(resolve, reject){  
		
	}  
}
returnPromise().then(res=>{})

6. Promise的基本用法

  1. .then(res=>{}, err=>{}) 用于执行回调函数
  2. .catch() 抓取reject(错误/异常) 等价于 .then(null, (err)=>{})
  3. .all() 处理多个promise对象
    Promise.all([promise1, promise2]).then().catch() 接受一个数组,数组中可以有多个promise对象
    当数组中所有promise对象执行都成功则all方法也成功,若有一个不成功,则all方法不成功
  4. .race() 也是处理多个promise对象
    Promise.race([promise1, promise2]).then().catch() 接受一个数组,数组中可以有多个promise对象
    race的状态由第一个返回的promise对象的状态所决定,若第一个promise返回resolve则表示成功,若第一个返回reject则表示失败
  5. .done()/.finally() 不管最后结果是失败还是成功都会执行
  6. .resolve()/.reject() 将任何数据类型转换成promise对象 eg. let p = Promise.resolve(‘foo’)

7. Promise并发

1. 并发请求
const urls = [
  'bytedance.com',
  'tencent.com',
  'alibaba.com',
  'microsoft.com',
  'apple.com',
  'hulu.com',
  'amazon.com'
];
function loadUrl(url) {
    return new Promise((res, rej)=>{
        const img = new Image();
        img.src = url;
        img.onload = function() {
            console.log('图片资源加载完成');
            res();
        };
        img.onerror = rej;
    })
}
Promise.all(urls.map(loadUrl(url))).then(res=>{}); //使用map方法遍历

Promise并发执行多个请求,并且所有请求全部加载完成后再处理数据

2. 限制数量的并发

假设这么一个业务场景:有多个资源的url并存储在一个urls数组中,要求并发下载并且任意时刻同时下载的链接数量不可以超过三个

const urls = [
  'bytedance.com',
  'tencent.com',
  'alibaba.com',
  'microsoft.com',
  'apple.com',
  'hulu.com',
  'amazon.com'
];

urls中存储了加载资源的URL

function loadUrl(url) {
    return new Promise((res, rej)=>{
        const img = new Image();
        img.src = url;
        img.onload = function() {
            console.log('图片资源加载完成');
            res();
        };
        img.onerror = rej;
    })
}

函数 loadUrl 加载图片资源,输入一个url并且返回一个Promise,该Promise在图片下载完成时resolve,下载失败reject

let count = 0; //记录并发执行的数量
// 并发加载函数
function currentLoad() {
    if (urls.length > 0 && count < 3){  // 判断是否超过一定限制
        count++;
        loadUrl(urls.shift()).then(()=>{
            count--;
        }).finally(currentLoad)  // .finally结束时进行下一次的加载
    }
}
function run() {
    // 开始时执行三次
    for (let i = 0; i < 3; i++){
        currentLoad();
    }
}
run();

8. Promise和Async的联系和区别

Promiseasync都是异步编程的方法,是非阻塞的

联系:async/await实质上是构建在Promise之上的,我们使用async定义的函数最终将会返回一个Promise对象,其then方法中接收的值为async函数中return的内容

async function asyncReq(){
	await console.log("async");
	return "done";
}
// Promise {<resolved>: "done"}
asyncReq().then((res)=>{console.log(res)}); //done

区别:

  1. async/await具有更好的语义性,提高代码可读性
    async代表函数内部有异步操作
    await代表需要等待后面的异步操作
  2. async/await写起来更像是同步,直接按照想要的顺序进行代码书写;而Promise只能在then方法中进行回调函数的执行
  3. 对于错误处理,async/await可以同时捕获异步和同步代码抛出的异常,而在promise中,then函数里抛出的异常try/catch是捕获不到的,只能用.catch方法捕获
const makeRequest = () => {
  try {
    getJSON()
      .then(result => {
        // then方法中可能会出错,但是try/catch模块中的catch不能够捕获错误
        const data = JSON.parse(result)
        console.log(data)
      })
	  // 必须放在这个catch方法中
      .catch((err) => {
        console.log(err)
      })
  } catch (err) {
    console.log(err)
  }
}
const makeRequest = async () => {
	try {
		// 同步代码和异步代码都可以被catch捕获
		const data = JSON.parse(await getJSON())
		console.log(data)
	} catch (err) {
		console.log(err)
	}
}