Promise的缺点:

  • 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
  • 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
  • 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

    Promise深入 + 自定义Promise

    promise本身是同步的,它的then方法和catch方法是异步的

1. 准备

1.1. 函数对象与实例对象

  1. 1. 函数对象: 将函数作为对象使用时, 简称为函数对象
  2. 2. 实例对象: new 函数产生的对象, 简称为对象

1.2. 回调函数的分类

  1. 1. 同步回调:
  2. 理解: 立即执行, 完全执行完了才结束, 不会放入回调队列中
  3. 例子: 数组遍历相关的回调函数 / Promiseexcutor函数
  4. 2. 异步回调:
  5. 理解: 不会立即执行, 会放入回调队列中将来执行
  6. 例子: 定时器回调 / ajax回调 / Promise的成功|失败的回调

1.3. JS中的Error

  1. 1. 错误的类型
  2. Error: 所有错误的父类型
  3. ReferenceError: 引用的变量不存在
  4. TypeError: 数据类型不正确的错误
  5. RangeError: 数据值不在其所允许的范围内
  6. SyntaxError: 语法错误
  7. 2. 错误处理
  8. 捕获错误: try ... catch
  9. 抛出错误: throw error
  10. 3. 错误对象
  11. message属性: 错误相关信息
  12. stack属性: 函数调用栈记录信息

2. Promise的理解和使用

2.1. Promise是什么?

  1. 1.抽象表达:
  2. PromiseJS中进行异步编程的新的解决方案(旧的是谁?)旧的是回调函数
  3. 2.具体表达:
  4. 从语法上来说: Promise是一个构造函数
  5. 从功能上来说: promise对象用来封装一个异步操作并可以获取其结果
  6. 3. promise的状态改变(改变只有2种, 只能改变一次)(状态是promise实例对象的一个属性就是promiseState,状态有3
  7. 分别是 pending 未决定的、resolved/fullfilled成功、rejected 失败)
  8. pending变为resolved
  9. pending变为rejected
  10. 无论变为成功还是失败,都会有一个结果数据,成功的结果数据一般为value,失败的结果数据一般为reason
  11. 4.实例对象的另一个属性是promiseResult:保存着异步任务成功或者失败的结果,只有reject()和resolve()才可以改变它
  12. 5.异步操作:fs文件操作(node下的模块 对文件进行读写操作)、数据库操作、ajax、定时器
  13. 6. promise的基本流程

promise - 图1
2022-02-16_180633.png

2.2. 为什么要用Promise?

  1. 1. 指定回调函数的方式更加灵活: 可以在请求发出甚至结束后指定(一个或多个)回调函数
  2. 旧的: 必须在启动异步任务前指定
  3. 新的:promise: 启动异步任务 => 返回promie对象 => promise对象绑定回调函
  4. 数(甚至可以在异步任务结束后指定/多个)
  5. 2. 支持链式调用, 可以解决回调地狱问题
  6. 问题:
  7. 回调地狱:回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调执行的条件(不便于阅读,不便于异常处理)
  8. 回调地狱解决方案:promise 链式调用。终极解决方案:async/await

2.3. 如何使用Promise?

  1. 1. 主要API
  2. new Promise(excutor) excutor称为执行器函数,在Promise内部是同步调用的(如图)
  3. Promise构造函数: Promise (excutor) {}
  4. Promise.prototype.then方法: (onResolved, onRejected) => {}
  5. Promise.prototype.catch方法: (onRejected) => {}(只能执行失败的回调,不能执行成功的回调,相当于半个then方法)
  6. Promise.resolve方法: (value) => {}(是属于promise函数对象的,并不属于实例对象。作用是为了更快的得到promise对象,而且
  7. 还能封装一个值,将这个值转化为promise对象;结果:非promise类型的对象则返回成功的promise,如果是promise类型则返回结果由promise类型决定)
  8. Promise.reject方法: (reason) => {}(描述同上,不同的是它返回的一直都是失败的promise类型的对象,失败的值为你传入的参数,
  9. 即便传入的是成功的promise对象,结果也是失败)
  10. Promise.all方法: (promises) => {}(接收一个promise数组,返回的结果是一个promise对象,只有所有的promise对象都是成功的才会返回成功的promise数组,
  11. 否则有一个失败,则返回的结果是失败的那个promise对象的状态和结果)
  12. Promise.allSettle方法:(promises)=> {}(promise.allSettled:接收一个promise数组,返回的结果是一个promise对象,也永远是一个成功的状态,
  13. 里面的值是每一个promise对象成功的状态和结果。)
  14. promise.allSettledpromise.all都可以做批量异步任务的场景,如果想得到每个任务的结果 promise.allSettled,如果想每个任务都成功才能执行下去 promise.all
  15. Promise.race方法: (promises) => {}(它的结果由第一个改变状态的promise对象来决定,接收也是数组)
  16. 2. 几个重要问题
  17. 如何改变promise的状态?1.resolve() 2.reject() 3.throw
  18. 一个promise指定多个成功/失败回调函数, 都会调用吗? promise改变对应的状态时都会调用,不改变状态不调用(指定回调就是then方法)
  19. promise.then()返回的新promise的结果状态由什么决定?由指定回调函数(then内部的回调函数)的执行结果决定
  20. 改变promise状态和指定回调函数谁先谁后? 都有可能 1.先改变状态后指定回调:当执行器函数里面是一个同步任务时(里面是resolve()和reject()时),
  21. 当指定回调更晚执行时 2.先指定回调后改变状态:当执行器函数是个异步任务时(加了定时器)
  22. 什么时候拿到数据? 指的是什么时候执行回调代码 1.当先改变状态后指定回调时立即执行回调代码 2.当先指定回调后改变状态时,
  23. 状态改变完之后执行回调代码。
  24. promise如何串连多个操作任务? 通过then的链式调用串联多个同步异步任务
  25. promise异常传(穿)透? 当使用了promisethen链式调用时,可以在最后指定失败的回调(一般是catch方法),
  26. 前面任何操作出了异常,都会传到最后失败的回调中处理
  27. 中断promise链?.then().then().then()就是promise 在回调函数中返回一个pending状态的promise对象(因为状态不改变,后面的回调都不能执行)
  28. return new Promise(()=>{})即可

nodejs中内置的一个方法util.promisify:传入一个遵循常见的错误优先的回调风格的函数(即以(err,value)=>…)回调作为最后一个参数,并返回一个promise版本
也就是说:
以后的每一个方法我们不需要手动封装,可以借助util.promisify方法将原来那种回调函数风格的 转变成promise风格的这种函数
const util = require(‘util’)
const fs = require(‘fs’)
let result = util.promisify(fs.readFile)
result(‘./文本文件/age.md’).then(value=>{
console.log(value.toString())
})

执行器函数在内部是同步调用的:
2022-02-16_181055.png
Promise.resolve():
2022-02-16_181919.png

Promise.reject():
2022-02-16_182315.png
image.png
Promise.all()和Promise.allSettlt():
image.png
2022-02-16_182554.png
Promise.race方法:
image.png
Promise的几个关键问题:
2022-02-16_183531.png
2022-02-16_183831.png2022-02-16_184502.png2022-02-16_190620.png2022-02-16_190643.png2022-02-16_191131.png2022-02-16_191211.png2022-02-16_192530.png2022-02-16_192837.png

3. 自定义Promise

  1. 1. 定义整体结构
  2. 2. Promise构造函数的实现
  3. 3. promise.then()/catch()的实现
  4. 4. Promise.resolve()/reject()的实现
  5. 5. Promise.all/race()的实现
  6. 6. Promise.resolveDelay()/rejectDelay()的实现
  7. 7. ES6 class版本

1.整体结构:
image.png
2.resolve和reject代码实现(包括状态的改变和对输出值的设置):
image.png
3.改变状态第三点:抛出异常实现
image.png
4.promise对象的状态只能改变一次:如果已经更改就不能再改了
image.png
5.then方法执行回调:
image.png
6.异步任务回调的执行:
image.png
image.png
7.指定多个异步任务的实现:
image.png
image.png
8.同步修改then方法的返回结果:
image.png
image.png
9.异步修改then方法的返回结果:
image.png
image.png
10.封装then方法:
image.png
11.catch方法和异常穿透和值传递:
image.png
2022-02-17_223133.png
12.封装Promise.resolve和Promise.reject方法:
image.png
13.封装Promise.all方法:
image.png
14.Promise.race方法:
image.png
15.指定回调的异步执行(then方法的异步执行):
image.png
image.png
16.最终类的封装:
class Promise {
//改变状态以及异步回调
constructor(executer) {
//executer为函数类型的参数
//以下两个属性是实例对象的 所以用this
this.PromiseStatus = ‘pending’
this.PromiseValue = null
this.callbacks = []
//用箭头函数的原因是 保存实例函数的this,下面函数的this指的是window
const resolve = data => {
// 判断状态:如果状态已经改过,就不能再改了
if (this.PromiseStatus !== ‘pending’) return
//做两个事情1.改变对象的状态 2.设置对象的结果值
this.PromiseStatus = ‘resolved’
this.PromiseValue = data
// 当改变状态是异步执行时,在改变完状态后执行回调(状态为成功的回调在这里执行)
// if (this.callbacks.onResolved) {
// this.callbacks.onResolved(data)
// }
//
//为了保证then()方法的异步执行,采用加定时器的方式解决,因为定时器本身就是异步的
setTimeout(() => {
for (let index of this.callbacks) {
index.onResolved(data)
}
})
}
const reject = data => {
// 判断状态:如果状态已经改过,就不能再改了
if (this.PromiseStatus !== ‘pending’) return
//做两个事情1.改变对象的状态 2.设置对象的结果值
this.PromiseStatus = ‘rejected’
this.PromiseValue = data
//为了保证then()方法的异步执行,采用加定时器的方式解决,因为定时器本身就是异步的
setTimeout(() => {
for (let index of this.callbacks) {
index.onRejected(data)
}
})
}
try { //捕获抛出的错误,错误放在catch里面处理,代码区如果有错误就会返回异常的处理结果
executer(resolve, reject) //执行器函数在内部是同步调用的,其次两个参数也是函数类型
} catch (e) {
reject(e)
}
}
//Promise.then
then(onResolved, onRejected) {
const that = this
//异步异常穿透报错的原因是在回调函数中我们没有指定失败的回调,导致失败的回调是一个undefined,之后在改变状态后再执行失败的回调时会报错,每次传递都会报错。解决的办法时 判断当发现失败的回调不存在时就给失败的回调设置默认值为抛出错误
if (typeof onRejected !== ‘function’) {
onRejected = reason => {
throw reason
}
}
//值传递和异常穿透的原理一样 不过值传递传的是值 不是异常
if (typeof onResolved !== ‘function’) {
onResolved = value => value
}
return new Promise((resolve, reject) => {
//封装then方法 注意保存this
function callback(type) {
try {
let result = type(that.PromiseValue) //第三步
if (result instanceof Promise) {
result.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(result)
}
} catch (e) {
reject(e)
}
}
if (this.PromiseStatus === ‘resolved’) {
//为了保证then()方法的异步执行,采用加定时器的方式解决,因为定时器本身就是异步的
setTimeout(() => {
callback(onResolved)
})
}
if (this.PromiseStatus === ‘rejected’) {
//为了保证then()方法的异步执行,采用加定时器的方式解决,因为定时器本身就是异步的
setTimeout(() => {
callback(onRejected)
})
}
if (this.PromiseStatus === ‘pending’) {
this.callbacks.push({
onResolved: function () {
callback(onResolved)
},
onRejected: function () {
callback(onRejected)
}
})
}
})

}
//Promise.catch
catch (onRejected) {
return this.then(undefined, onRejected)
}
//Promise.resolve
static resolve(value){
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(v => {
resolve(v)
}, r => {
reject(r)
})
} else {
resolve(value)
}
})
}
//Promise.reject
static reject(reason){
return new Promise((undefined, reject) => {
reject(reason)
})
}
//Promise.all
static all(promises){
return new Promise((resolve, reject) => {
//声明一个变量记录数组里元素的个数
let count = 0
// 声明一个最后输出成功的结果数组
const arr = []
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => {
count++
arr[i] = v //将每个promise对象元素成功的值保存到数组中
if (count == promises.length) {
resolve(arr)
}

}, r => {
reject(r)
})

}
})
}
//Promise.race
static race(promises){
return new Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(v => { //哪个promise对象元素先执行就将最终的返回结果的状态和值 改成先执行的这个,并且只能改变一次无法覆盖
resolve(v)
}, r => {
reject(r)
})
}
})
}
}

4. async与await(axios和util和动态improt 均返回promise对象)

await 表达式的运算结果取决于它等的是什么。

  • 如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。
  • 如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
  1. 1. async 函数
  2. 函数的返回值为promise对象
  3. promise对象的结果由async函数执行的返回值决定
  4. 2. await 表达式
  5. await右侧的表达式一般为promise对象, 但也可以是其它的值
  6. 如果表达式是promise对象, await返回的是promise成功的值
  7. 如果表达式是其它值, 直接将此值作为await的返回值
  8. 3. 注意:
  9. await必须写在async函数中, async函数中可以没有await
  10. 如果awaitpromise失败了, 就会抛出异常, 需要通过try...catch来捕获处理

async和await结合读取文本文件:
image.png
async和await结合发送ajax请求:
image.png

async/await对比Promise的优势

  • 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调⽤也会带来额外的阅读负担
  • Promise传递中间值⾮常麻烦,⽽async/await⼏乎是同步的写法,⾮常优雅
  • 错误处理友好,async/await可以⽤成熟的try/catch,Promise的错误捕获⾮常冗余
  • 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。

    5. JS异步之宏队列与微队列

promise - 图42

  1. 1. 宏列队: 用来保存待执行的宏任务(回调), 比如: 定时器回调/DOM事件回调/ajax回调(之前学的回调都是宏任务)
  2. 2. 微列队: 用来保存待执行的微任务(回调), 比如: promise的回调/MutationObserver的回调
  3. 3. JS执行时会区别这2个队列
  4. JS引擎首先必须先执行所有的初始化同步任务代码
  5. 每次准备取出第一个宏任务执行前, 都要将所有的微任务一个一个取出来执行

并发与并行的区别?

  • 并发是宏观概念,我分别有任务 A 和任务 B,在一段时间内通过任务间的切换完成了这两个任务,这种情况就可以称之为并发。
  • 并行是微观概念,假设 CPU 中存在两个核心,那么我就可以同时完成任务 A、B。同时完成多个任务的情况就可以称之为并行。

以上代码在浏览器环境中,如果定时器执行过程中出现了耗时操作,多个回调函数会在耗时操作结束以后同时执行,这样可能就会带来性能上的问题。
如果有循环定时器的需求,其实完全可以通过 requestAnimationFrame 来实现:
function setInterval(callback, interval) { let timer const now = Date.now let startTime = now() let endTime = startTime const loop = () => { timer = window.requestAnimationFrame(loop) endTime = now() if (endTime - startTime >= interval) { startTime = endTime = now() callback(timer) } } timer = window.requestAnimationFrame(loop) return timer } let a = 0 setInterval(timer => { console.log(1) a++ if (a === 3) cancelAnimationFrame(timer) }, 1000) 复制代码
首先 requestAnimationFrame 自带函数节流功能,基本可以保证在 16.6 毫秒内只执行一次(不掉帧的情况下),并且该函数的延时效果是精确的,没有其他定时器时间不准的问题,当然你也可以通过该函数来实现 setTimeout。

6.面试题:

面试题1

2022-02-18_221719.png

面试题2

2022-02-18_222932.png

面试题3

2022-02-18_224753.png

面试题4

2022-02-18_234905.png2022-02-18_235026.png

面试题5

image.png
image.png

image.png

image.png
function getJson(){
return new Promise((reslove,reject) => {
setTimeout(function(){
console.log(2)
reslove(2)
},2000)
})
}
async function testAsync() {
await getJson()
console.log(3)
}

testAsync()

相当于:

function getJson(){
return new Promise((reslove,reject) => {
setTimeout(function(){
console.log(2)
reslove()
},2000)
})
}

function testAsync() {
return new Promise((reslove,reject) => {
getJson().then(function (res) {
console.log(3)
})
})
}

testAsync()

async getAsyncConstant() {
return 1
}
相当于:
function getAsyncConstant() {
return Promise.resolve().then(function () {
return 1;
});
}

async getPromise() {
return new Promise((resolved, rejected)=> {
resolved(1)
});
}
相当于:
function getPromise() {
return Promise.resolve().then(function () {
return new Promise((resolved, rejected) => {
resolved(1);
});
});
}

promise可以使用.then(res)=>{函数体}的方式,将上一个函数返回的结果res作为参数传递到下一个回调函数中去,这样就能保证上一个函数执行完成,之后 再去执行下一个函数。
async函数的返回值是promise对象,可以用.then方法进行下一步操作。
async函数可以看作是多个异步操作,包装成一个promise对象。
await命令就是内部then命令的语法糖(就是await后面异步函数.then(await下一步操作))
异步的循环一般不用forEach,用for of
then+回调函数
1.async函数返回一个Promise对象
2.可以使用then方法添加回调函数。
3.当函数执行的时候,一旦遇到await就会先返回
4.等到异步操作完成,再接着执行函数体后面的语句,相当于给await后面的语句加了异步的定时器。
5.async函数内部return语句返回的值,会成为then方法回调函数的参数,
6.await后是一般函数 相当于先调用函数 函数内如果有输出 会先输出,再等待
async function test(){
concole.log(‘qcq’)
}

let result = await test()
console.log(result)

会先输出qcq,再undefined 因为test里面没有返回值。(test函数中的输出是放在执行器函数中是同步的)await相当于then 而test().then 中test里面如果有输出 就会先输出 里面的执行器函数是同步的

//串行:请求是异步的,需要等待上一个请求成功,才能执行下一个请求
//并行:同时发送多个请求「HTTP请求可以同时进行,但是]S的操作都是一步步的来的,因为JS是单线程」,等待所有请求都成功,我们再去做什么事情?
Promise.all([axios.get(‘/user/list’),
axios.get(‘/user/list’),
axios.get(‘/user/list’)]).then(results =>console.log(results);})
.catch(reason =>})

并发:把拿到的结果先存起来,可以使用await一个一个执行,也可以使用promise.all依次执行
let result = await promise.all([ ])
await 相当于promise.all([ ]).then
image.png
image.png


async function dbFuc(db) {
let docs = [{}, {}, {}];

// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
上面代码会报错,因为 await 用在普通函数之中了。但是,如果将 forEach 方法的参数改成 async 函数,也有问题。
async function dbFuc(db) {
let docs = [{}, {}, {}];

// 可能得到错误结果
docs.forEach(async function (doc) {
await db.post(doc);
});
}

上面代码可能不会正常工作,原因是这时三个 db.post 操作将是并发执行,也就是同时执行,而不是继发执行。正确的写法是采用 for 循环。
async function dbFuc(db) {
let docs = [{}, {}, {}];

for (let doc of docs) {
await db.post(doc);
}
}
如果确实希望多个请求并发执行,可以使用 Promise.all 方法。
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

let results = await Promise.all(promises);
console.log(results);
}

// 或者使用下面的写法

async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));

let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}

四:async/await和Promise
上面说了async/await和Generator的关系,这里再说一下和Promise的关系,async/await其实是基于Promise的。async函数其实是把Promise包装了一下。
下面是一个async/await的写法:
getConstant() {
return 1
}

async getAsyncConstant() {
return 1
}
相当于:
function getAsyncConstant() {
return Promise.resolve().then(function () {
return 1;
});
}

async getPromise() {
return new Promise((resolved, rejected)=> {
resolved(1)
});
}
相当于:
function getPromise() {
return Promise.resolve().then(function () {
return new Promise((resolved, rejected) => {
resolved(1);
});
});
}

async test() {
let a = 2
let c = 1
await getConstant();
let d = 3
await getPromise();
let d = 4
await getAsyncConstant();
return 2
}
上面的代码其实真正的在解析执行的时候是这样的:
function getConstant() {
return 1;
}

function getAsyncConstant() {
return Promise.resolve().then(function () {
return 1;
});
}

function getPromise() {
return Promise.resolve().then(function () {
return new Promise((resolved, rejected) => {
resolved(1);
});
});
}

test() {
return Promise.resolve().then(function () {
let a = 2;
let c = 1;
return getConstant();
}).then(function () {
let d = 3;
return getPromise();
}).then(function () {
let d = 4;
return getAsyncConstant();
}).then(function () {
return 2;
});
}
通过上面的代码可以看出async/await的本身还是基于Promise的。
因为await本身返回的也是一个Promise,它只是把await后面的代码放到了await返回的Promise的.then后面,以此来实现的。
做个练习:
function getJson(){
return new Promise((reslove,reject) => {
setTimeout(function(){
console.log(2)
reslove(2)
},2000)
})
}
async function testAsync() {
await getJson()
console.log(3)
}

testAsync()

相当于:

function getJson(){
return new Promise((reslove,reject) => {
setTimeout(function(){
console.log(2)
reslove()
},2000)
})
}

function testAsync() {
return new Promise((reslove,reject) => {
getJson().then(function (res) {
console.log(3)
})
})
}

testAsync()
上面的代码是会先打印2还是3?
答案是2,3
看过上面的童鞋应该知道其实他的真实代码是这样的:
function getJson(){
return new Promise((reslove,reject) => {
setTimeout(function(){
console.log(2)
reslove()
},2000)
})
}

function testAsync() {
return new Promise((reslove,reject) => {
getJson().then(function (res) {
console.log(3)
})
})
}

testAsync()