本来以为对promise已经有一定的了解了,但是春招面试过程中还是状况不断,因此决定写一篇文章来做个总结。、
1. Promise的作用
Promise对象可以理解为一次执行的异步操作,使用promise对象之后可以使用一种链式调用的方式来组织代码;让代码更加的直观。也就是说,有了Promise对象,就可以将异步操作以同步的操作的流程表达出来,避免了层层嵌套的回调函数。总结一下就是可以将原先不可控的回调通过promise转为更加可控更清晰的方式表达,更加高效,更便于维护。
简而言之,Promise能用链式编程处理异步问题。
2. 使用方式
-
例子:一个高阶函数,将回调函数转换为promise
function promisify(fn) { return function (...arg) { return new Promise((resolve, reject) => { fn(...arg, (err, result) => { if (err) { reject(err); } resolve(result) }); }) }}使用示例
promisify(fn)(1, 2).then(res => { console.log(res)})也可以使用async/await语法糖
(async () => { let res = await promisify(fn)(1, 2) console.log(res)})() -
例子:异步任务链
makePromise1() .then(function (value) { console.log(value); return makePromise2(); }) .then(function (value) { console.log(value); return makePromise3(); }) .then(function (value) { console.log(value); });function makePromise1() { var p = new Promise(function (resolve, reject) { //异步操作 setTimeout(function () { console.log('异步1'); resolve('异步1参数'); }, 2000); }); return p;}function makePromise2() { var p = new Promise(function (resolve, reject) { //异步操作 setTimeout(function () { console.log('异步2'); resolve('异步2参数'); }, 2000); }); return p;}function makePromise3() { var p = new Promise(function (resolve, reject) { //异步操作 setTimeout(function () { console.log('异步3'); resolve('异步3参数'); }, 2000); }); return p;} -
异常捕获
promise的reject结果(错误信息) 会沿着链传递,直到被捕获
Promise.reject(1).then(res => { }).then(res => { }, err => { console.log(err)})也可以使用catch
Promise.reject(1).then(res => { }).catch(err => { console.log(err) })这里需要注意的是,catch其实是个语法糖,所以只是用来统一捕获没有捕获的错误信息,如果错误已经被捕获,则不会在catch中出现。
then函数的第二个参数,可以用来捕获前面所抛出的错误。
Promise.resolve(1).then(res => { throw new Error('') }).then(res => { }, err => { console.log('then 捕获', err) }).catch(err => { console.log('catch 捕获', err) })
3.Promise并发限制
并发限制的原理就是两个队列,一个放待执行的任务,一个放待返回的任务。
class TaskPool { constructor(max = 2) { this.max = max; this.pool = [] this.tasks = [] // 存放待处理的任务 } addTask(task) { this.tasks.push(task) this.run() } run() { // 获取当前需要执行的任务数 let min = Math.min(this.tasks.length, this.max - this.pool.length) for (let i = 0; i < min; i++) { let currentTask = this.tasks.shift() this.pool.push(currentTask) currentTask() .then(res => { }).catch(err => { console.log(err) }) .finally(() => { this.pool.splice(this.pool.indexOf(currentTask), 1) this.run() }) } }}var pool = new TaskPool(5);for (let i = 0; i < 50; i++) { pool.addTask(function () { return new Promise(resolve => { setTimeout(() => { resolve(i + 1); }, 1000); }).then(res => { console.log('resolved', res); }) })}
4.事件队列相关
关于promise的事件循环也是面试中的高频题目,这里向大家推荐我学习时的博客。zhuanlan.zhihu.com/p/33058983
总结一下promise相关的点:
- 异步任务分为宏任务和微任务。定时器、浏览器事件、processTask等都是宏任务;而Promise就是微任务的典型代表,不过Promise本身的回调函数属于同步任务。
- 微任务和宏任务都有自己的任务队列,浏览器每次执行代码时,遇到异步代码,不会等待结果返回而会继续向下执行。
- 异步任务会交给其他线程执行,当结果返回后推入相应的任务队列。
- 执行栈中的同步代码执行完毕后,会优先清空微任务队列,执行完一个宏任务后也会优先清空微任务。而将微任务的回调代码放入执行栈并重复上述过程的机制称为事件循环