如何理解 JS 中的异步

了解异步前,先得知道什么是单线程

单线程-只有一个线程,只能做一件事

代码示例

console.log(1)
alert('hello')
console.log(2)

点击确认后,才会打印出 2

JS 采用的是单线程,JS 修改 DOM 结构后,浏览器需要渲染,JS执行的时候,浏览器 DOM 渲染也会暂停,若是多线程的话,JS 同时修改 一个 DOM ,浏览器不知道如何渲染

但是单线程模式又无法满足一些需求,例如,获取后台数据,若是单线程模式的话,需要等到数据返回,才能做其他事情,此时若是网络不好,接下来的操作也无法执行了

所以出现了异步解决方案。

异步

代码示例

setTimeout(function() {
  console.log(1)
}, 1000)
setTimeout(function() {
  console.log(2)
})
console.log(3)

异步在 JS 中是如何实现的呢?通过 event-loop。

event-loop

事件轮询,状态变化的过程,参考nodejs文档,JS 实现异步的原理,同步代码直接执行,异步代码先放在异步队列中,待同步代码执行完毕后,轮询执行异步队列中的代码。

以上代码的执行过程

  1. function() { console.log(2) } 立即放入异步队列中,1s 后 function() { console.log(1) } 被放入
  2. 先执行主进程中的 console.log(3) ,执行完后,把先放入的拿到主进程中
  3. 在主进程中依次执行 function() { console.log(2) },function() { console.log(1) }
  4. 最后输出 '3 2 1'

只要主线程空了,就会去读取“异步队列”,这个过程是不断重复的,称之为 event-loop。

event-loop 详细解析

继续看上面的代码,发现一些问题

  • 没按照书写方式,从上往下执行,可读性差
  • 回调函数不容易模块化,容易出现回调地狱

着重说一下回调地狱

看一个例子

function fn1() {
  setTimeout(()=>{
    console.log('fn1')
  }, 1000)
}

function fn2() {
  setTimeout(()=>{
    console.log('fn2')
  }, 1000)
}

function fn3() {
  setTimeout(()=>{
    console.log('fn3')
  }, 1000)
}

对于以上代码如何实现: 1s之后输出 fn1, 再过1s输出 fn2, 再过1s输出 fn3 ?

做如下改造

function fn1(callback) {
  setTimeout(()=>{
    console.log('fn1')
    callback()
  }, 1000)
}

function fn2(callback) {
  setTimeout(()=>{
    console.log('fn2')
    callback()
  }, 1000)
}

function fn3() {
  setTimeout(()=>{
    console.log('fn3')
  }, 1000)
}

fn1(function(){
  fn2(function(){
    fn3()
  })
}) //先执行 fn1() 再执行 fn2(),最后执行 fn3()

调用时,回调函数层层嵌套,易读性非常差,这就是回调地狱。

如何解决这些问题呢?Promise 可以解决

Promise

promise是对异步调用的封装,是一种异步的解决方案。

基本语法

function fn() {
  return new Promise((resolve,reject)=> {
    resolve() //成功时调用
    reject() //失败时调用
  })
}

fn().then(success, fail).then(success2, fail2)

promise 是一个对象,对象里存贮一个状态,这个是可以随着内部的执行转化的,为以下三种状态之一:等待态(Pending), 完成态(Fulfilled), 决绝态(Rejected)

一开始,先设置好 等状态从pending 变成 fullfilled 和 rejected 的预案(也就是当成功后做什么,失败时做什么)

promise 启动后,当满足成功条件时让状态从 pending 变成 fullfilled (执行 resolve)当满足失败的条件时,我们让状态从 pending 变成 rejected (执行 reject)

解决上述问题

function fn1() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('fn1...')
      resolve() //成功时的回调
    }, 1000)
  })
}

function fn2() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('fn2...')
      resolve() //成功时的回调
    }, 1000)
  })
}

function fn3() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('fn3...')
      resolve()
    }, 1000)
  })
}

function onerror() {
  console.log('error')
}

fn1()
  .then(fn2)
  .then(fn3) 
  .catch(onerror) //依次输出:fn1... fn2... fn3...

上述代码把 callback 的嵌套执行改为 then 的串联执行,可读性好了些。

Promise.all

接收一个 promise 对象的数组,待全部成功后,就会调用 success

Promise.all([promise1, promise2]).then(success, fail)

Promise.race

接收一个 promise 对象的数组,promise1 和 promise2 只要有一个成功后,就会调用 success

Promise.race([promise1, promise2]).then(success, fail)

then 只是将 callback 拆分了,还是存在回调函数,当 then 多了后,可读性依然有些差,async await 可以解决这问题。

async await

async 是对 promise 的扩展,同步的写法去使用,再也没有回调函数

var w = waitHandle()
w.then(success1,fail1)
  .then(success2,fail2)
  .then(success3,fail3)

改为

const success = async function(){
  const result = await waitHandle()
  console.log(result)
}
success()

同步的写法,可读性更好了。

捕获异常

多个await命令,可以统一放在try...catch结构中。

async function main() {
  try {
    const val1 = await firstStep();
    const val2 = await secondStep(val1);
    const val3 = await thirdStep(val1, val2);

    console.log('Final: ', val3);
  }
  catch (err) {
    console.error(err);
  }
}

转载于:

https://zhuanlan.zhihu.com/p/78816710

多谢分享

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值