全面掌握ECMAScript新特性——ES6异步编程(三)

JS是单线程的

异步编程其实就是处理异步任务,在进行异步编程之前,我们需要了解JavaScript是单线程的,在同一时间只能做一件事。

JavaScript之所以设计成单线程,是与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动以及操作DOM。这决定了它只能是单线程,否则会带来很多复杂的同步问题。例如,如果JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器就不晓得以哪个线程为准。所以,为了避免复杂,从一诞生,JavaScript就是单线程的。


单线程就意味着,所有任务都需要排队,前一个任务结束,后一个任务才会执行。那么如果前一个任务很长的话,那么后面一个任务不是就一直需要等待了吗?于是乎,JS将所有任务分成了两类,同步和异步。
同步:只有前一个任务执行完毕,才能执行后一个任务
异步:当同步任务执行到某个需要耗时执行的运行环境API时,就会触发异步任务,此时运行环境(浏览器或Node)就会单独开线程去处理这些异步任务。

JavaScript运行原理

下面是JavaScript运行原理图,同步任务在JS主线程完成,异步任务则新开一个线程


WechatIMG1861.png


疑问:不是说JavaScript是单线程的吗,那为什么又新开了一条线程处理任务呢,这不是多线程方式吗?
有这个疑问不奇怪,我们需要清楚JavaScript单线程其实说的是JavaScript引擎是单线程的,开发者只能通过单线程的方式进行JavaScript开发,而新开了一条线程处理任务是底层执行环境决定的,JavaScript执行环境是多线程。




在实际项目中,异步编程使用场景极其之多,请求个接口数据、创建个定时器、缓存个数据都离不开异步编程的身影,为了更好的处理异步任务,ES6给我们提供两种新的方式,分别是Promise和Generator。

Promise

Promise 是一个代理对象,代表了一个异步任务的最终成功或者失败状态。Promise允许你为异步任务的成功或失败分别设置对应的处理方法,以类似同步的方式便捷的进行异步编程。


一个Promise有三种状态:

  • pending:初始状态,既不是成功,也不是失败状态。
  • fulfilled:成功状态,代表着任务执行完成
  • rejected:失败状态,代表着任务执行失败

基本使用

创建Promise对象
const promise = new Promise(function (resolve, reject) {
    let result=执行异步任务;
    if(result){
        //如果异步任务成功完成
        resolve()
    }else{
        //如果异步任务执行失败
        reject();
    }
});

创建Promise对象需要传递一个executor参数,executor是带有resolve和reject两个参数的函数,这两个参数是JavaScript引擎提供的两个函数,Promise构造函数执行会立即调用executor函数。

  • 当Promise中的异步任务执行成功时,调用resolve(),将Promise对象的状态从pending改为fulfilled(未完成到成功)
  • 当Promise中的异步任务执行失败时,调用reject(),将Promise对象的状态从pending改为rejected(未完成到失败)


![image.png](https://img-blog.csdnimg.cn/img_convert/164a56af1903e48ddbc004a29f44f940.png#align=left&display=inline&height=463&margin=[object Object]&name=image.png&originHeight=463&originWidth=759&size=47507&status=done&style=none&width=759)

如何使用

实例好promise对象后,我们可以使用下面的语法来对异步任务完成后的状态进行处理。

promise.then(onFulfilled,onRejected)

promise.then(
  (result) => {
    console.log("异步任务处理成功,执行相应方法");
  },
  (error) => {
    console.log("异步任务处理失败,执行相应方法");
  }
);

但在具体的项目开发中,我们通常都是使用已经存在的Promise对象,下面我们就通过使用常用的promise对象fetch获取接口数据。

案例实现

我们需要调用接口,获取一个用户列表数据

fetch("http://jsonplaceholder.typicode.com/users")
  .then(function (response) {
    return response.json();
  })
  .then(function (res) {
    console.log(res);	// [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
  });

response是一个包含响应结果的Response对象,它只是一个HTTP响应,而不是真正的JSON。为了获取JSON的内容,需要使用json()方法获取一个Promise对象,然后再使用then获取JSON数据。

其他使用

Promise.prototype.catch()

Promise提供了catch()方法,用来捕获异步操作过程中遇到的错误异常,使用场景如下

const CatchPromise = new Promise(function (resolve, reject) {
  reject(new Error("error msg"));
});

CatchPromise.then().catch((e) => {
  console.error(e); //Error: error msg
});

在Promise对象中,除了可以使用reject(new Error())的方式触发异常,还可以使用throw new Error()的方式触发异常,但不建议使用throw new Error()的方式,因为这种方式不会改变Promise的状态。

Promise.prototype.all()

Promise.all()用于处理多个异步任务,例如处理多张图片上传。Promise.all()接受一个promise对象数组作为参数,执行完毕返回一个Promise对象。

Promise.all()的状态变化:
传入的promise对象数组全部变为fulfill状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject()

const promise1 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("promise1");
  }, 2000);
});
const promise2 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("promise2");
  }, 1000);
});
const promise3 = new Promise(function (resolve, reject) {
  setTimeout(function () {
    resolve("promise3");
  }, 3000);
});

const promiseAll = Promise.all([promise1, promise2, promise3]);
promiseAll.then(function (results) {
  console.log(results); // ["promise1", "promise2", "promise3"]
});


#### Promise.prototype.race() Promise.race()也是用于处理多个异步任务,与Promise.all()一样,Promise.race()接受一个promise对象数组作为参数,执行完毕返回一个Promise对象。

**Promise.race()的状态变化:**
传入的promise对象数组有一个变为resolve状态则返回成功,调用resolve()
传入的promise对象数组有一个变为reject状态则返回失败,调用reject() ```javascript const promise1 = new Promise(function (resolve, reject) { setTimeout(function () { reject("promise1"); }, 2000); }); const promise2 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise2"); }, 1000); }); const promise3 = new Promise(function (resolve, reject) { setTimeout(function () { resolve("promise3"); }, 3000); });

const promiseAll = Promise.race([promise1, promise2, promise3]);
promiseAll.then(function (results) {
console.log(results); // promise2
});



<a name="bOVQP"></a>
## Generator函数
Generator函数是用来处理异步任务的函数,函数内部包裹的就是异步任务的处理。Generator不同于普通函数,当执行到异步任务,可以暂停,直到异步任务执行完毕再继续往下执行,类似同步的方法进行异步编程。<br />

<a name="zRjaF"></a>
### 如何使用
Generator函数在使用上具体有以下特点

- *:定义generator函数时,function后面需要加上星号,例如function* generatorFuncName()
- yield:需要暂停去执行异步任务时,需要在代码前面加上yield标识,例如yield fetch()
- next():调用generator函数后获得的是一个指针对象,调用指针的next方法,可以用来分阶段逐步执行generator函数。


<br />那么具体如何使用generator函数实现异步编程呢,在学习Promise的时候,我们实现了一个获取一个用户列表数据的案例,下面我们看看如何使用generator函数实现吧。
```javascript
function* loadUsers() {
  const API = "http://jsonplaceholder.typicode.com/users";
  console.log("等待数据请求");
  yield fetch(API);	//暂停,开始执行异步任务
  console.log("数据请求完成");
  console.log("继续其他逻辑操作");
}

const generator = loadUsers();

const promise = generator.next().value;

console.log(promise);

promise
  .then(function (response) {
    return response.json();
  })
  .then(function (result) {
    console.log(result); //[{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
    generator.next(); //打印:数据请求完成  继续其他逻辑操作。异步任务执行完毕后调用next(),继续执行generator函数中后续代码
  });


更多文章内容,请关注公众号【方塘HCI】






参考资料
【1】JavaScript 运行原理解析
【2】JS是单线程,你了解其运行机制吗?
【3】史上最最最详细的手写Promise教程
【4】Generator 函数的含义与用法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值