出现背景
CodeWhy:新技术的出现,都是为了解决原有的某个痛点
先来看几个问题
例子1
function random () {setTimeout(() => { // 这个是setTimeout 里面有一个箭头函数return parseInt(Math.random() * 6) + 1}, 1000)// return undefined}console.log(random());
- random() 这个函数有 return 吗?
- 答 : 没有写return , 只会 return undefined
- setTimeout 里面有一个箭头函数,有 return 吗?
- 答 : 有写return , 返回真正的值
- 所以setTimeout 里面的箭头函数是 异步函数/任务
- 请分清random() 这个函数 和 setTimeout 里面的箭头函数
console.log(n) // undefined
- 为什么console.log(n) 得到undefined ?- 因为random() 没有 return- console.log(n)要立刻得到结果- 所以得到 undefined- 正确写法- 如何拿到 setTimeout 里面的箭头函数里面的异步结果 ?- 使用回调```javascriptfunction random (fn){setTimeout(()=>{fn(parseInt(Math.random() * 6))},1000)}//这个是一个回调 写个函数,然后把函数地址给它function fn1 (x){console.log(x)}//接收这个函数random(fn1)// 然后在求随机数函数得到结果后 把结果作为参数传给fn1//内部逻辑//相当于function (fn1){setTimeout(()=>{fn1(parseInt(Math.random() * 6)) //结果变成参数 传给 fn1},1000) //fn1 得到参数 => 打印}
随机函数得到 结果后把 结果 作为 参数 传给
fn1写法简化
- 由于fn1声明之后只用了一次,所以可以删掉 fn1 ```javascript function fn1(x){ console.log(x) } random(fn1)
//简化为 random( x => console.log(x) )
//再次简化 => 只有一个参数的时候才可以用 random( console.log )
- 注意- 如果参数个数不一致就不能这样简化<a name="pWCHW"></a>#### 关于简化有一个面试题- 这个简化了为什么打印出 NaN ?1. 这个代码正确写法```javascriptlet array = ['1', '2', '3'].map((item,i,arr) => {return parseInt(item)})console.log(array); //[1,2,3]
- 像面试题里面简写就相当于这样写
parseInt(string, radix)let array = ['1', '2', '3'].map((item, i, arr) => {return parseInt(item)// parseInt('1', 0, arr) => 1// parseInt('2', 1, arr) => NaN// parseInt('3', 1, arr) => NaN})console.log(array);
| 参数 | 描述 |
|---|---|
| string 必需 | 要被解析的字符串。 |
| radix 可选 | 表示要解析的数字的基数。该值介于 2 ~ 36 之间。 |
| 如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。 如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。 |
总结
- 异步任务不能拿到结果
- 于是我们传一个回调给异步任务
- 异步任务完成时调用回调
-
Promise
-
Promise出现之前处理异步函数
1、回调函数
在异步操作执行完后,执行一个函数告诉我们执行完了
function double(value, callback) { // 这里callback就是回调函数,什么名字都可以setTimeout(() => callback(value * 2), 1000);}double(3, (x) => console.log(`I was given: ${x}`));// I was given: 6(大约 1000 毫秒之后)
2、失败处理
成功回调和失败回调 ```javascript function double(value, success, failure) { setTimeout(() => { try { if (typeof value !== ‘number’) {
throw 'Must provide number as first argument';
} // 成功时执行 success(2 * value); } catch (e) { // 失败时执行 failure(e); } }, 1000); }
// 定义函数执行成功时,回调函数
const successCallback = (x) => console.log(Success: ${x});
// 定义函数执行失败时,回调函数
const failureCallback = (e) => console.log(Failure: ${e});
double(3, successCallback, failureCallback); double(‘b’, successCallback, failureCallback);
// Success: 6(大约 1000 毫秒之后) // Failure: Must provide number as first argument(大约 1000 毫秒之后)
<a name="oJ1DW"></a>#### 3、嵌套回调随着代码越来越复杂,回调策略是不具有扩展性的。“回调地狱”这个称呼可谓名至实归。<br />嵌套回调的代码维护起来就是噩梦<br />这三个问题也是上面出现背景中提到的问题<a name="mty2r"></a>### Promise(期约)<a name="OCbXY"></a>#### 是什么?- Promise是一个类,可以翻译成承诺、许诺、期约;- 当我们需要给予调用者一个承诺∶待会儿我会给你回调数据时,就可以创建一个Promise的对象;- 在通过new创建Promise对象时,我们需要传入一个回调函数,我们称之为executor;- 这个回调函数会被立即执行,并且给传入另外两个回调函数resolve、reject ;- 当我们调用resolve回调函数时,会执行Promise对象的then方法传入的回调函数;- 当我们调用reject回调函数时,会执行Promise对象的catch方法传入的回调函数;<a name="f9LcT"></a>#### 应用场景一般情况下是有异步操作时,使用 Promse对这个异步操作进行封装<a name="ZyX5h"></a>### 基本使用```javascriptlet a = new Promise((resolve,reject) =>{ // 通常命名为resolve解决,reject拒绝// 这里的函数会立刻执行,// 如果在函数里面执行了resolve(),后面就会执行.then 方法,不执行.catch 方法// 如果在函数里面执行了reject(),后面就会跳过.then 方法,直接执行.catch 方法// 例子这里放网络请求的代码let res = request() // 比如这是个例子if(res == true){ // 这里验证只是举个例子,实际要看返回的结果来作判断//请求成功的话,请求的结果通过resolve函数传到then里面resolve("abc")}else{//失败的话,把失败信息传到reject函数里面reject("fail")}}).then(data =>{//这里可以处理resolve传过来的数据console.log(data) // 这里例子就是上面resolve("abc")里面的"abc"}).catch(err =>{//这里可以获得reject传过来的错误信息,然后处理console.log(err) // 这里例子就是上面reject("fail")里面的"fail"})
Promise 的状态
Promise 是一个有状态的对象,可能处于如下 3 种状态之一:
1、待定(pending)
最初始状态,如果 Promise(()=>{ })里面的函数体,没有调用 resolve( ) 完成,或者没有调用reject( ) 失败,则会一直保持待定状态
只要出了结果,过程是不可逆的,无法重复修改结果。
2、兑现(fulfilled)
3、拒绝(rejected)
表示没有成功完成,此时会提示一定要有个catch方法捕获这个失败的信息
比如,假设Promise要向服务器发送一个 HTTP 请求。
请求返回 200~299 范围内的状态码就足以让Promise的状态变为“resolved”。
类似地,如果请求返回的状态码不在 200~299 这个范围内,那么就会把期约状态切换为“rejected”。
resolve 成功的处理
情况一
如果resolve传入一个普通的值或者对象,那么这个值会作为then回调的参数;
情况二
如果resolve中传入的是另外一个Promise,那么这个新Promise会决定原Promise的状态︰
情况三
如果resolve中传入的是一个对象,并且这个对象有实现then方法,那么会执行该then方法,并且根据then方法的结果来决定Promise的状态
then 方法
说明
then方法是Promise对象上的一个方法∶它其实是放在Promise的原型上的Promise.prototype.then
对象的方法:首先要new 一个对象,然后执行这个对象.方法()
方法参数
then方法接受两个参数∶
- fulfilled(resolve)的回调函数︰当状态变成fulfilled(resolve)时会回调的函数;
- reject的回调函数︰当状态变成reject时会回调的函数;
多次调用
返回值
1、普通值
如果返回值是基本数据类型、对象、数组、undefined(没有return返回)等,会当做新的Promise的resolve的值
相当于 
2、new Promise
如果返回一个new Promise,则后面的状态(.then .catch),由这个返回的Promise里面的状态决定
3、对象且实现then方法
还是会包一层new Promise 然后返回
相当于
链式调用
原理就是内部会包一层 new Promise 然后返回,下面的就会接收上面的返回值,进行调用(见上面 返回值)
catch 方法(错误处理)
说明
catch方法也是Promise对象上的一个方法∶它也是放在Promise的原型上的Promise.prototype.catch
对象的方法:首先要new 一个对象,然后执行这个对象.方法()
捕获错误
除了Promise里面调用reject方法会执行catch,抛出错误(throw)也会触发catch
- 这里相当于包装了一层new Promise 然后 return,详见then方法里面的返回值章节
- then( ) 的第二个参数就是catch( )方法
- 运行结果:
多次调用
和then一样,可以同一个对象多次调用,这样会以同一个结果来执行多个catch方法
let promise = new Promise((resolve, reject) => {reject('aaa')});promise.catch(err => {console.log('err:', err);})promise.catch(err => {console.log('err:', err);})//err: aaa//err: aaa
链式调用
参考 then 方法里面的链式调用,只要在链式调用的过程中,出现了 reject 或者 出现了异常new Error 或者 代码报错,则会跳过下面所有的.then 方法,直接执行.catch( ) 方法(或者某个then() 方法的第二个参数,也是catch方法)
返回值
catch 也有返回值,也是相当于包装了一层 new Promise,然后return出去,因此还可以在后面接then 和 catch
finally 最终执行(ES9)
- finally是在ES9(ES2018 )中新增的一个特性∶表示无论Promise对象无论变成fulfilled还是reject状态,最终都会被执行的代码。
- finally方法是不接收参数的,因为无论前面是fulfilled状态,还是reject状态,它都会执行。
Promise 的类方法
上面介绍的then、catch、finally方法都属于Promise的实例方法,都是存放在Promise的prototype上的。下面总结一下Promise的类方法。
通过 Promise . 类方法( ) 的方式调用
resolve( ) 直接成功
// 类方法Promise.resolve()let promise = Promise.resolve({ name: '张三' });// 相当于let promise2 = new Promise((resolve, reject) => {resolve({ name: '张三' })})
直接使用 Promise 这个类的方法,可以简化 Promise 的编写。
它的参数和 new Promise 里面resolve的是一样的。见上《resolve 成功的处理》。
reject( ) 直接失败
- reject方法类似于resolve方法,只是会将Promise对象的状态设置为reject状态。
- Promise.reject的用法相当于new Promise,只是会调用reject
Promise.reject传入的参数无论是什么形态,都会直接作为reject状态的参数传递到catch的。
// 类方法Promise.reject()Promise.reject('张三');// 相当于new Promise((resolve, reject) => { reject('张三') })
直接使用 Promise 这个类的方法,可以简化 Promise 的编写。
all( ) 等到一起完成
它的作用是将多个Promise包裹在一起形成一个新的Promise ;
let p1 = new Promise((resolve, reject) => {resolve('111');})let p2 = new Promise((resolve, reject) => {resolve('222');})let p3 = new Promise((resolve, reject) => {resolve('333');})
Promise会等到 p1、p2、p3都完成(只要不是待定pending状态,就是成功或失败)后,再执行后面then 或 catch的内容
Promise.all([p1, p2, p3]).then(res => {console.log(res);//['111', '222', '333']})
新的Promise状态由包裹的所有Promise共同决定︰
- 当所有的Promise状态变成fulfilled状态时,新的Promise状态为fulfilled,并且会将所有Promise的返回值组成一个数组;
- 当有一个Promise状态为reject时,新的Promise状态为reject,并且会将第一个reject的返回值作为参数;
类似与&&操作,所有都成功,返回的就是成功;只要有一个失败,就会立刻返回的就是失败,其他没完成的就不执行了。
- 成功的话,会把所有返回的结果,放在数组里面
-
allSettled( ) 等到一起完成(ES11)
all方法有一个缺陷︰当有其中一个Promise变成reject状态时,新Promise就会立即变成对应的reject状态。
- 那么对于resolved的,以及依然处于pending状态的Promise,我们是获取不到对应的结果的;
在ES11 (ES2020 )中,添加了新的API Promise.allSettled :
- 该方法会在所有的Promise都有结果( settled ),无论是fulfilled,还是reject时,才会有最终的状态
- 并且这个Promise的结果一定是fulfilled的;
最终返回的是一个数组,里面按数组的顺序陈列结果和传递的值let p1 = new Promise((resolve, reject) => {resolve('111');})let p2 = new Promise((resolve, reject) => {resolve('222');})let p3 = new Promise((resolve, reject) => {resolve('333');})Promise.allSettled([p1, p2, p3]).then(res => {console.log(res);
race( ) 第一个完成的
race是竞技、竞赛的意思,表示多个Promise相互竞争,谁先有结果,那么就使用谁的结果;
谁先完成就用谁的结果,这个结果是成功的,就调用then;这个结果是失败的,就调用catchlet p1 = new Promise((resolve, reject) => {resolve('111');})let p2 = new Promise((resolve, reject) => {resolve('222');})let p3 = new Promise((resolve, reject) => {resolve('333');})Promise.race([p1, p2, p3]).then(res => {console.log(res);}).catch(err => {console.log(err);})
any( ) 任意成功的 (ES12)
any方法是ES12中新增的方法,和race方法是类似的:
- any方法会等到一个fulfilled状态,才会决定新Promise的状态;
- 如果所有的Promise都是reject的,那么也会等到所有的Promise都变成rejected状态;

如果所有的Promise都是reject的,那么会报一个AggregateError的错误。
然后执行catch,把这个错误传入catch;
你还可以通过err.errors 这个属性获取上面所有Promise 返回的错误值
其他特性
1、比常规代码推后执行*
let p1 = Promise.resolve();p1.then(() => console.log('5'));console.log('1');let p2 = Promise.reject();p2.then(null, () => console.log('6'));console.log('2');let p3 = Promise.reject();p3.catch(() => console.log('7'));console.log('3');let p4 = Promise.resolve();p4.finally(() => console.log('8'));console.log('4');// 输出为// 1 2 3 4 5 6 7 8
Promise 的代码,会比一般常规的代码,退后执行(如上例子),是因为 JavaScript 的事件循环机制。
1、首先,代码由上往下,一条一条执行,遇到一般的代码,比如创建变量、执行普通函数等,都会在左边的 内存里面的 堆/栈 结构里面执行。
2、遇到Promise、setTimeout等,就会把这些代码放进下方的回调队列里面排队,等一般代码执行完后,再执行这里队列的。
因此如上例子,才会出现,先输出1234(一般代码,堆/栈里面执行),再输出5678(进队列里面,后面再执行)
2、执行顺序
如果给Promise添加了多个处理程序,当期约状态变化时,相关处理程序会按照添加它们的顺序依次执行
let p1 = Promise.resolve();let p2 = Promise.reject();p1.then(() => setTimeout(console.log, 0, 1));p1.then(() => setTimeout(console.log, 0, 2));// 1// 2p2.then(null, () => setTimeout(console.log, 0, 3));p2.then(null, () => setTimeout(console.log, 0, 4));// 3// 4p2.catch(() => setTimeout(console.log, 0, 5));p2.catch(() => setTimeout(console.log, 0, 6));// 5// 6p1.finally(() => setTimeout(console.log, 0, 7));p1.finally(() => setTimeout(console.log, 0, 8));// 7// 8
3、超时失败
设定一个时间,超过时间还没完成就返回拒绝。
因为Promise的状态只能改变一次,所以超时拒绝逻辑中可以放心地设置让Promise处于待定状态的最长时间。
如果执行器中的代码在超时之前已经解决或拒绝,那么超时回调再尝试拒绝也会静默失败。
let p = new Promise((resolve, reject) => {// 先设置超时时间,如 10 秒后调用 reject()setTimeout(reject, 10000);// 再执行函数的逻辑// ...});

(多次调用,共用同一个结果res)
