Promise、async、await 的基本使用及理解

Promise、async、await 的基本使用及理解

promise

参考

前期知识

函数对象、实例对象

1. 函数对象

函数对象:将函数作为对象使用时,简称为函数对象

// 函数对象
function Person(){
}
Person.age = 0;
console.log(Person.age)
// 输出: 0

//小坑Person.name = Person 无法修改

2.实例对象

实例对象: new 构造函数或类产生的对象,我们称之为实例对象

function Person(name, age){
  this.name = name;
  this.age = age;
}
const p1 = new Person("小六", 5);
console.log(p1);
// 输出
// Person {name: '小六', age: 5}

在上述的代码中 p1 即为实例对象

回调函数的分类

回调的定义

自己定义的,我们没有调用,最终执行了

分类

  1. 同步回调函数:

    理解:立即在主线程上执行,不会放入回调的队列中

    例子: 数组遍历相关的回调函数/ Promise的executor

  2. 异步的回调函数:

    理解: 不会立即执行,会放入回调队列中以后执行

    例子:定时器/ ajax 回调 / Promise 的成功、 失败的回调

同步的回调函数

// 同步的回调
let arr = [1, 2, 3, 4, 5, 6, 7]
arr.forEach((i)=>{
  console.log(i)
})

console.log("主线程上的代码");

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzHIykf7-1655978702667)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623130508302.png)]

异步回调函数

setTimeout(()=>{
  console.log("dd")
}, 1000);

console.log("主线程上的代码");

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QESfIkWW-1655978702669)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623130627399.png)]

js 中的error

相关文档: MDN

  1. 错误的类型

    • Error: 所有错误的父类型

    • ReferenceError: 引用变量不存在

    • TypeError: 数值类型不正确

    • RangeError: 数值不在其所允许范围内 (死循环)

    • SyntaxError: 语法错误

  2. 错误处理

    捕获错误: try{} catch(){}

    抛出错误; throw error

  3. 错误对象

    message属性: 错误相关信息

    stack属性: 记录信息

几种类型错误的示例

// ReferenceError :引用变量不存在
/* console.log(a) */

// TypeError: 数据类型不正确
/* const demo = () => {}
demo()() */

// RangeError: 数据值不在其所允许的范围内
/* const demo = ()=>{demo();}
demo(); */

// SyntaxError: 语法错误
/* console.log(1; */

错误处理

try {
  console.log(1);
  console.log(a);
  console.log(2);
} catch (e) {
  console.log("错误了")
}

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ItRDktiz-1655978702669)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623133207839.png)]

错误的抛出

function demo(){
  const date = Date.now();
  if(date % 2 == 0) {
    console.log("偶数时间")
  } else {
    throw new Error("奇数时间")
  }
}

try{
  demo();
} catch (e) {
  console.log("err : ",e)
}

promise 的理解和使用

理解

  1. 抽象表达:

    Promise: 是JS中进行异步编程的新方案 (旧的方案是纯回调)

  2. 具体表达:

    • 从语法上来说: Promise 是一个构造函数
    • 从功能上来说: Promise 对象用来封装一个异步的操作并可以获取其结果

使用

简单案例一,Promise 的创建

  1. new Promise 的时候传入一个回调函数,它是同步的回调,会立即在主线程上执行,它被称为executor函数
  2. 每一个Promise实例都有三种状态,分别为: a. 初始化(pending) b.成功(fulfilled) c.失败(rejected)
  3. 每一个Promise实例在刚被new出来的那一刻,状态都是初始化的
const p = new Promise(() => {
  console.log("this run");
});
console.log("运行结束")

输出结果

this run

运行结束

结果分析

说明上述的代码是同步执行的,也就是executor函数是同步的

简单案例二(resolve, reject,状态的指定)

状态只能执行一次

executor 函数会接受到2个参数,它们都是函数,分别用形参: resolve, reject 的形式接受

声明方式 : new Promise(executor)

  • 调用resolve, 会让Promise 实例状态变为: 成功(fulfilled),同时可以指定成功的value
  • 调用reject, 会让Promise实例状态变为: 失败(rejected), 同时可以指定失败的reason

代码

const p = new Promise((resolve, reject) => {
  resolve("ok");
})
console.log(p);

const e = new Promise((resolve, reject) => {
  reject("错误");
})
console.log(e);

输出

Promise {: ‘ok’}

Promise {: ‘错误’}

简单案例三

代码

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1000);
  },1000)
})

p.then(
    (value) => console.log("success",value) ,  // 成功的回调
    (reason) => console.log("err",reason)          // 失败的回调
    );

console.log("end")

输出

end

success 1000

案例四(使用Promise发送请求并获取数据)

代码

var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;

const p = new Promise((resolve, reject) => {
  const xhr = new XMLHttpRequest();
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4) {
      if (xhr.status === 200) resolve(xhr.responseText);
      else reject("请求出错");
    }
  }
  xhr.open("GET", "https://api.apiopen.top/api/getHaoKanVideo")
  xhr.responseType = "json";
  xhr.send()
})

p.then(
    (value) => console.log("success1",value) ,  // 成功的回调
    (reason) => console.log("err1",reason)          // 失败的回调
  )

console.log("end")

输出

成功的输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUVGaBJw-1655978702670)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623155859086.png)]

错误的输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XAmboMux-1655978702672)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623155834075.png)]

理解

其实到这里我们可以理解Promise的作用是帮助我们封装一个异步的函数,并且可以根据异步函数执行的状态,如果成功执行就返回数据,错误执行的话就返回错误的原因(当然这些都可以根据需求而定)。并且我们通过一个.then函数可以很方便的获取。

案例五 (使用Promise对案例四进行封装)

通过案例四我们可以通过Promise进行执行一个异步函数,并且获取回调(异步)的结果,不过案例四中的各种参数写的比较固定,因此我们对它进行简单的封装。

代码

var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;

function myAxios(method,url){
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
      if(xhr.readyState === 4) {
        if (xhr.status === 200) resolve(xhr.responseText);
        else reject("请求出错");
      }
    }
    xhr.open(method, url)
    xhr.responseType = "json";
    xhr.send()
  })
}

myAxios("GET", "https://api.apiopen.top/api/getHaoKanVideo")
      .then(res => console.log(res),
            err => console.log(err),
      )

console.log("end")

分析

输出结果: 与案例四一致

优点: 可以看到我们封装了myAxios函数来执行这个异步的任务并且获取了执行结果。封装之后代码的灵活度更高,复用性更高(比如我们要切换不同的url发送请求就不用重新写一个Promise了)。

案例六 (catch回调函数的意义)

代码1

const p = new Promise((resolve, reject) => {
  throw new Error("出错!")
  reject("错误啦");
})

p.then(res => console.log(res))
  .catch(e => console.log("err->",e));

console.log("end")

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSXIZukk-1655978702673)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623164837021.png)]

代码2(等价于代码1)

const p = new Promise((resolve, reject) => {
  throw new Error("出错!")
  reject("错误啦");
})

p.then(res => console.log(res))
  .then(null,e => console.log("err->",e));

console.log("end")

代码3(回调函数中出现异常)

const p = new Promise((resolve, reject) => {
  resolve(200);
})

p.then(res => console.log(a))
  .catch(e => console.log("err->",e));

console.log("end")

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zYh1lDQx-1655978702674)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623165144161.png)]

分析

可以从上述的案例中,可以看出catch触发的条件是, 当我们传入的函数也就是executor 或者then回调函数运行出错或者触发reject的时候,可以通过catch获取抛出的错误。

案例七 (Promise.all的使用 )

使用方法

Promise.all(promiseArr)

说明

返回一个新的Promise实例,只有当所有的promise都成功才成功(成功的结果以数组的形式返回),只要有一个失败就失败。

代码1(成功执行)

const dataArr = [1,2,3,4,5];
const arr = dataArr.map(item => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(item + 1);
  },500)
}))

Promise.all(arr).then(res => console.log(res), err => console.log(err));
console.log("end")

运行结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXImBXxn-1655978702674)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623170431088.png)]

代码2(部分失败运行)

const p = Promise.reject(-200);
const promiseArr = [1,2,p];
const arr = promiseArr.map(item => new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(item);
  },500)
}))
Promise.all(arr).then(res => console.log(res))
                .catch(err => console.log("err->", err));
console.log("end")

输出结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1wi1Kjlj-1655978702675)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623170956595.png)]

案例八(Promise.race的使用)

使用方法

Promise.race(promiseArr)

说明

返回一个新的Promise实例,成功还是失败以最先出结果的promise为准(也就是说这里race的是时间)。

代码1(成功运行)

const p0 = Promise.resolve(200);
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(-200);
  },500);
})
Promise.race([p1,p0]).then(res => console.log("success ->", res))
                    .catch(err => consol.log("err->", err));

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GiMvO603-1655978702675)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623172234913.png)]

代码2(失败运行)

const p0 = Promise.reject(200);
const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(-200);
  },500);
})
Promise.race([p1,p0]).then(res => console.log("success ->", res))
                    .catch(err => console.log("err->", err));

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SsOgpLRT-1655978702676)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623172416297.png)]
作用

给一个异步任务设置超时时间,例如axios中的timeout可以通过race实现

案例九(_then的链式调用)

说明

  1. 如果then所指定的回调返回值是非Promise值a:

    那么新Promise状态为:成功,且值为a

  2. 如果then所指定的回调返回值是一个Promise实例p:

    那么新Promise实例的状态和值,与p一致

  3. 如果then所指定的回调抛出异常:

    那么新Promise实例状态为rejected, reason 为抛出的异常

代码

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(200);
  },500);
})
const x = p.then(res => { 
    console.log("ok1",res)
    return new Promise((resolve, reject) => {
      resolve(1000);
    })
  })
  .catch(err => {
    console.log("err1",err)
    return Promise.reject(err)
  })
x.then(res => console.log("ok2",res))
  .catch(err => console.log("err2",err));

输出

ok1 200
ok2 1000

案例十(中断Promise链式调用)

中断promise链的方式

  1. 在使用promise的then链式调用时,在中间中断,不再调用后面的回调函数
  2. 在失败的问题函数中返回一个 pendding 状态的Promise实例

代码

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(200);
  },500);
})
p.then(
  res => Promise.reject(-200),
  err => err)
  .then(
    res => res,
    reason => {
      console.log("中断链条-> ",reason)
      return new Promise(()=>{})
    })
  .then(
    res => console.log("ok"),
    reason => console.log("err"),
  )
console.log("end");

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LNt1z7ba-1655978702676)(C:\Users\86136\AppData\Roaming\Typora\typora-user-images\image-20220623175556638.png)]

案例十一(async、await的使用)

代码

const p0 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(-200);
  })
})
const p = async () => {
  try {
    return await p0;
  } catch (error) {
    console.log("err->", error)
  }
}
p()

输出

err -> -200

分析

asyncawait是配套使用的,其中大致可以理解为async保证了对应函数的异步。await是阻塞对应进程并等待结果(与前面代码通过then调用的效果类似)。

案例十二 (async、await解决链式调用问题)

代码

/**
 * 
 * @param {区分是谁调用的} id 
 * @param {手动设置回调的成功与否} state 
 * @returns 
 */
function task(id, state=true) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      state ? resolve(id) : reject(id);
    },1000);
  });
}
const p = async() => {
  try {
    const res1 = await task(1);
    console.log(res1);
    const res2 = await task(2);
    console.log(res2);
    const res3 = await task(3,false);
    console.log(res3);
    const res4 = await task(4);
    console.log(res4);
  } catch (err) {
    console.log("出了点错", err);
  }
}
p();
console.log("end");

输出结果

end

1
2
出了点错 3

结果及分析

可以看到数据依次在控制台输出,并且当执行task3的时候由于回调出错导致task4并没有执行。实现了链式调用的问题。
要点:

  • 在这里await起到的作用可以理解为阻塞当前的线程,等待对应的异步任务结束。
  • try{}catch(){}起到了错误穿透的作用。
  • p这个函数依然是异步函数。

总结

要重点理解其中哪些是异步哪些是同步的,以及链式调用的过程。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值