前言:个人学习笔记,初识Promise
1.Promise解决了什么问题?
用来解决 回调地狱 问题
回调地狱:在JS中,为了实现某些逻辑经常会写出层层嵌套的 回调函数,如果嵌套过多,会极大的影响代码可读性和逻辑,这种情况被称为回调地狱。比如说把一个函数A作为回调函数,但是该函数又接收一个函数B作为参数,甚至B还能接受函数C作为参数,这样层层嵌套,人们称为回调地狱,代码阅读性非常的差。
|
|
回调函数:回调是一个函数,它作为参数传递给另一个函数,并在其父函数完成后执行。
|
|
回调既可以同步回调,也可以异步回调。
|
|
感受:JS虽然灵活,但是灵活的同时,带来的缺点就是,写代码的过程中需要加一些判断,比如上面这段代码中判断callback是不是函数,在别的强类型语言中,这个callback如果不是函数,可能写的时候编辑器就会直接提醒,传入参数的类型不对。所以就会出现TypeScript。
注意回调函数中的this指向:
|
|
上面这段代码中回调函数的this是指向window的,此时可以使用call或apply改下this的指向
|
|
问题:JS底层是怎么实现异步的,相关内容:V8引擎是如何实现异步的,EventLoop相关原理深度解析,JS执行过程中的堆栈变化,异步是不是等同于并行以及其它相关底层问题。
链式调用:同一对象多次其属性或方法的时候,我们需要多次书写对象进行“.” 或 () 操作;链式调用是一种简化此过程的一种编码方式,使代码简洁、易读。
函数柯里化:
2.Promise使用
资料相关:
- 1.使用 Promise
- 2.Promise
Promise是一个对象,代表一个异步操作的最终完结或失败。
(1)Promise特点
1.在本轮 事件循环 运行完成之前,回调函数是不会被调用的。
2.即使异步操作已经完成(成功或失败),在这之后通过 then() 添加的回调函数也会被调用。
3.通过多次调用 then() 可以添加多个回调函数,它们会按照插入顺序进行执行。
4.Promise 很棒的一点就是链式调用(chaining)(Promise可以是使用链式调用)
(2)链式调用
连续执行两个或者多个异步操作是一个常见的需求,在上一个操作执行成功之后,开始下一个的操作,并带着上一步操作所返回的结果。我们可以通过创造一个 Promise 链来实现这种需求。基本上,每一个 Promise 都代表了链中另一个异步过程的完成。
注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。
(3)Catch 的后续链式操作
有可能会在一个回调失败之后继续使用链式操作,即,使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作会很有用。
|
|
打印结果:
注意:因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。
(4)错误传递
通常,一遇到异常抛出,浏览器就会顺着 Promise 链寻找下一个 onRejected 失败回调函数或者由 .catch() 指定的回调函数。这和以下同步代码的工作原理(执行过程)非常相似。
通过捕获所有的错误,甚至抛出异常和程序错误,Promise 解决了回调地狱的基本缺陷。这对于构建异步操作的基础功能而言是很有必要的。
(5)Promise 拒绝事件
当 Promise 被拒绝时,会有下文所述的两个事件之一被派发到全局作用域(通常而言,就是window;如果是在 web worker 中使用的话,就是 Worker 或者其他 worker-based 接口)。
(1)rejectionhandled
当 Promise 被拒绝、并且在 reject 函数处理该 rejection 之后会派发此事件。
(2)unhandledrejection
当 Promise 被拒绝,但没有提供 reject 函数来处理该 rejection 时,会派发此事件。
以上两种情况中,PromiseRejectionEvent 事件都有两个属性,一个是 promise 属性,该属性指向被驳回的 Promise,另一个是 reason 属性,该属性用来说明 Promise 被驳回的原因。
因此,我们可以通过以上事件为 Promise 失败时提供补偿处理,也有利于调试 Promise 相关的问题。在每一个上下文中,该处理都是全局的,因此不管源码如何,所有的错误都会在同一个处理函数中被捕捉并处理。
一个特别有用的例子:当你使用 Node.js 时,有些依赖模块可能会有未被处理的 rejected promises,这些都会在运行时打印到控制台。你可以在自己的代码中捕捉这些信息,然后添加与 unhandledrejection 相应的处理函数来做分析和处理,或只是为了让你的输出更整洁。举例如下:
|
|
调用 event 的 preventDefault() 方法是为了告诉 JavaScript 引擎当 Promise 被拒绝时不要执行默认操作,默认操作一般会包含把错误打印到控制台,Node 就是如此的。
理想情况下,在忽略这些事件之前,我们应该检查所有被拒绝的 Promise,来确认这不是代码中的 bug。
(6)在旧式回调 API 中创建 Promise
理想状态下,所有的异步函数都已经返回 Promise 了。但有一些 API 仍然使用旧方式来传入的成功(或者失败)的回调。典型的例子就是 setTimeout() 函数:
|
|
混用旧式回调和 Promise 可能会造成运行时序问题。如果 saySomething 函数失败了,或者包含了编程错误,那就没有办法捕获它了。这得怪 setTimeout。
幸运地是,我们可以用 Promise 来封装它。最好的做法是,将这些有问题的函数封装起来,留在底层,并且永远不要再直接调用它们:
|
|
通常,Promise 的构造器接收一个执行函数(executor),我们可以在这个执行函数里手动地 resolve 和 reject 一个 Promise。既然 setTimeout 并不会真的执行失败,那么我们可以在这种情况下忽略 reject。
(7)组合
Promise.resolve() 和 Promise.reject() 是手动创建一个已经 resolve 或者 reject 的 Promise 快捷方法。它们有时很有用。
Promise.all() 和 Promise.race() 是并行运行异步操作的两个组合式工具。
我们可以发起并行操作,然后等多个操作全部结束后进行下一步操作,如下:
|
|
可以使用一些聪明的 JavaScript 写法实现时序组合:
|
|
通常,我们递归调用一个由异步函数组成的数组时,相当于一个 Promise 链:
|
|
我们也可以写成可复用的函数形式,这在函数式编程中极为普遍:
|
|
composeAsync() 函数将会接受任意数量的函数作为其参数,并返回一个新的函数,该函数接受一个通过 composition pipeline 传入的初始值。这对我们来说非常有益,因为任一函数可以是异步或同步的,它们能被保证按顺序执行:
|
|
在 ECMAScript 2017 标准中, 时序组合可以通过使用 async/await 而变得更简单:
|
|
8.时序
为了避免意外,即使是一个已经变成 resolve 状态的 Promise,传递给 then() 的函数也总是会被异步调用。
9.嵌套
简便的 Promise 链式编程最好保持扁平化,不要嵌套 Promise,因为嵌套经常会是粗心导致的。可查阅下一节的常见错误中的例子。
10.常见错误
在编写 Promise 链时,需要注意以下示例中展示的几个错误:
|
|
一个好的经验法则是总是返回或终止 Promise 链,并且一旦你得到一个新的 Promise,返回它。下面是修改后的平面化的代码:
|
|
使用 async/await 可以解决以上大多数错误,使用 async/await 时,最常见的语法错误就是忘记了 await 关键字。
3.Promise
一个Promise对象在被创建出来的时候,不一定值是什么,异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者。
(1)Promise状态
1.待定(pending):初始状态,既没有兑现,也没有拒绝。 2.已兑现(fulfilled):意味着操作成功完成。 3.已拒绝(rejected):意味着操作失败。
因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回的是 promise, 所以它们可以被链式调用。
resolved: 已决议(resolved),它表示 promise 已经处于已敲定(settled)状态,或者为了匹配另一个 promise 的状态被"锁定"了。
注意:resolved不在Promise的三种状态之中
(2)Promise()构造函数
用于包装还没有添加 promise 支持的函数。
4.Promise方法
(1)Promise.all()
只有当参数里的所有异步操作都返回成功的时候,Promise.all()才会返回成功,否则,有一个错误,就会返回错误。
语法:
|
|
参数:
一个可迭代对象,如 Array 或 String。
返回值:
1.传入空的可迭代对象时,返回一个已完成状态的Promise
2.传入的参数不包含Promise,返回一个异步完成的Promise
3.其它情况下返回一个处理中(pending)的Promise
使用:
|
|
|
|
(2)Promise.allSettled()
该Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的promise结果。
当您有多个彼此不依赖的异步任务成功完成时,或者您总是想知道每个promise的结果时,通常使用它。
参数:
一个可迭代的对象,例如Array,其中每个成员都是Promise。
注意:此方法需要谷歌76版本以上才会支持
(3)Promise.any()
只要Promise中有一个返回成功就返回成功,所有的都失败才会返回失败,跟 Promise.all()方法正好相反, any 方法和 all 方法有点类似于或和且的关系。
此方法需要谷歌85版本以上才会支持
(4)Promise.race()
Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。
只要可迭代对象中有一个返回,Promise.race()就会立马返回这个状态的值,返回的也是一个Promise
(5)Promise.reject()
Promise.reject()方法返回一个带有拒绝原因的Promise对象。
参数:
reason:表示Promise被拒绝的原因。
返回值:一个给定原因了的被拒绝的 Promise。
(6)Promise.resolve()
Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象。
警告:不要在解析为自身的thenable 上调用Promise.resolve。这将导致无限递归,因为它试图展平无限嵌套的promise。一个例子是将它与Angular中的异步管道一起使用。在此处了解更多信息。
参数:
value:将被Promise对象解析的参数,也可以是一个Promise对象,或者是一个thenable。
返回值:
返回一个带着给定值解析过的Promise对象,如果参数本身就是一个Promise对象,则直接返回这个Promise对象。
5.Promise原型
(1)Promise.prototype.catch()
添加一个拒绝(rejection) 回调到当前 promise, 返回一个新的promise。当这个回调函数被调用,新 promise 将以它的返回值来resolve,否则如果当前promise 进入fulfilled状态,则以当前promise的完成结果作为新promise的完成结果。返回一个Promise,所以catch后面可以继续使用then。
(2)Promise.prototype.then()
添加解决(fulfillment)和拒绝(rejection)回调到当前 promise, 返回一个新的 promise, 将以回调的返回值来resolve.
then() 方法返回一个 Promise。它最多需要有两个参数:Promise 的成功和失败情况的回调函数。
(3)Promise.prototype.finally()
finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。 这避免了同样的语句需要在then()和catch()中各写一次的情况。
返回值:
返回一个设置了 finally 回调函数的Promise对象。
6.创建Promise
|
|
7.async/await
async/await时ES8新增的
8.generator 和 yield
9.try…catch
3.涉及到的面试题
(1)Promise.then里抛出的错误能否被try…catch捕获,为什么?
不能,因为try catch只能处理同步的错误,对异步错误没有办法捕获
try catch为什么不能捕获异步错误?
因为try执行时从上到下的,异步代码没有返回,try…catch 怎么捕获
then 的第二个参数和使用 catch 的区别
主要区别就是,如果在 then 的第一个函数里抛出了异常,后面的 catch 能捕获到,而第二个函数捕获不到
|
|
也就是说 then 里面抛出的还是需要后面的catch去捕获,而then的第二个参数回调函数是在之前返回的结果为 rejected 时调用的
那 then 之后的错误如何捕获呢。
|
|
只能是在回调函数内部 catch 错误,并把错误信息返回,error 会传递到下一个 then 的回调。
拓展:promise 内部的错误不会冒泡出来,而是被 promise 吃掉了,只有通过 promise.catch 才可以捕获,所以用 Promise 一定要写 catch 啊。
(2)你怎么解决“回掉地狱”的问题?你对Proxy,和Promise的理解,在哪里用到过?
(3)请手写实现一个 promise?
(4)淘宝等网站的倒计时功能如何实现?
(5)写一个 promise 重试函数,可以设置时间间隔和次数
(6)手写 Promise.all
(7)手写并发
(8)promise讲解,all和race的作用
(9)promise输出
(10)迭代器的,yield 和 yield*的区别。还有promise的race。
(11)promise的日常应用
(12)简单描述一下promise,并说明如何在外部进行resolve()
(13)Promise的作用及用法以及内部实现原理?
(14)说一下你对 generator 的了解?
(15)promise.then().then() ,promise.catch().then() ok 不?
(16)js实现带并发限制的调度器,其实就是使用promise限制并发
(17)如何理解 Promise?Promise 对象的含义?
(18)对async和await的理解
(19)vue promise原理
(20)vue promise 同时发起
(21)js 并发请求
(22)如何破坏promise链?
(23)catch 之后的 then 还会执行吗?
会继续执行,因为catch方法返回的是promise,所以后续的then还会继续执行,当然如果在catch中手动返回 Promise.reject(),那后续的then就不会被调用了。