Js中为什么需要异步

一、为什么JS需要异步


JavaScript是单线程语言,同一时间只能做一件事,JS执行和DOM渲染公用一个线程,当JS执行时会阻塞DOM解析和DOM渲染。

当我们发出网络请求或者定时器任务,需要时间可能很长,这时候不能让页面卡住,因为可能会造成页面空白,让用户长时间的等待,这是极其影响用户体验的,所以需要异步

二、异步和同步


同步就是当执行到一个同步任务,必须等待这个任务执行完毕,之后的代码才能继续执行。

异步执行到异步任务,主线程会将异步任务先挂起,继续执行后续代码,而这个异步任务会交给其他线程进行处理,处理完毕后会讲处理后的结果放到任务队列里,当主线程空闲时就会按照任务队列里顺序使用异步任务的回调函数进行处理。

eg.

console.log('start')
// 一个定时器
setTimeout(() => {
  console.log('done')
}, 1000)
console.log('end')
/**
 * 执行打印结果
 * start
 * end
 * done
 */

上述代码中的定时器就是一个异步任务,效果是延迟1秒后打印done

我们可以看到执行结果done是最后打印的,说明执行到这个异步任务时并没有阻塞后续代码的执行,而是先打印end,最后异步任务执行完成后再打印的done

除了setTimeout外,还有setInterval、ajax请求、DOM事件等都是异步

三、异步的几种方案


1. 回调函数

回调就是达到条件后,用事先设定好的函数进行处理

如下代码:

console.log('start')
// 一个定时器
setTimeout(() => {
  console.log('done')
}, 1000)
console.log('end')
/**
 * 执行打印结果
 * start
 * end
 * done
 */

 

代码中的定时器,异步任务就是延迟1秒,然后执行回调函数,回调函数里是打印done

2. 事件监听

有事件源和监听器,通过监听器不断监听事件源,如果满足条件监听器就会执行设定好的函数处理

如下代码:

window.onresize = () => {
  console.log('resize')
}

回调函数和事件监听区分:

1. 回调函数不存在事件源,或者说事件源和监听器是同一个,满足条件后事件源自己进行处理;

2. 而事件监听满足条件后则需要监听器进行处理。

通俗来说:我去女朋友家找女朋友,她没在

1. 回调:留下纸条,女朋友回来后看到纸条主动通知我回来了

2. 事件监听:找人留意这件事,当女朋友回来后通知我

3. Promise

3.1 为什么会有Promise

通过回调函数实现异步存在“回调地狱”的问题

试想如果有一个异步方法handle,我们需要创建四个异步任务A、B、C、D,我们需要实现如下效果,四个任务执行顺序必须是A、B、C、D,也就是说B必须等待A完成才能开始,C必须等待D完毕才能开始,依次类推

console.log('start')
// 一个定时器
setTimeout(() => {
  console.log('done')
}, 1000)
console.log('end')
/**
 * 执行打印结果
 * start
 * end
 * done
 */

我们实现如上伪代码,我们会发现在回调函数里一层嵌套着一层,随着以后业务功能的增加,嵌套层数会不止4层,而且回调函数里处理逻辑会越来越复杂,这样的代码会变得难以阅读理解,而且逻辑会变得非常混乱,增加维护成本,甚至根本无法维护。

所以promise随之诞生。

3.2 什么是Promise

Promie是ES6推出的一种异步方案,可以让我们以同步的方式写异步代码。

Promise是一个类,代表着未来才知道结果的事件

他有三个状态:pending(进行中)、fulfilled(成功)、rejected(失败)

当异步代码执行完毕后,调用对应的方法改变状态,然后就会自动执行对应状态的回调处理函数

3.3 Promise的特点

1. 对象状态不受外界影响。只能通过new时提供的resolve和reject方法才能改变状态

2. 状态一旦发生变化,就不会再变。

3.4 简单使用

1. 当我们new一个promise时,参数是一个函数,函数里提供了两个方法:resolve和reject

执行resolve我们可以将状态由 pending => fulfilled

执行reject我们可以将状态由 pending => rejected

2. promise实例上有两个方法then和catch

then方法接受两个函数作为参数,第一个是成功回调,第二个是失败回调

cacth接受一个函数作为参数,是代码执行异常抛出处理回调

3. resolve和reject函数执行可以传参,这个参数数据会被作为到then中对应的回调函数的参数

eg.

// 延迟ms毫秒执行
const walk = ms => new Promise((resolve, reject) => setTimeout(resolve, ms))

walk(1000)
  .then(() => {
    // 成功回调函数
    console.log('fulfilled')
  }, () => {
    // 失败回调函数
    console.log('rejected')
  })
  .catch(() => {
    // 异常回调函数
    console.log('error haved happend')
  })

如上代码,walk函数是生成一个promise实例,异步代码是一个定时器,在ms毫秒后讲promise实例的状态从pending => fulfilled,最后就会执行then中的第一个回调函数打印fulfilled

3.5 then中也是回调函数,但为什么不会产生回调地狱问题

因为promsie的所有方法最后return的都是一个新的promise实例,也就是说我们可以链式调用

拿then举例,then函数最后return值有三种:

1. 显式return一个promie,下一个then里的两个回调就会根据这个返回的promise状态来相应的执行

2. return一个非promsie值,then实际会返回一个成功状态的promsie,而且传递给下一个then中成功回调函数的参数就是这个非promsie值

3. 没有显式return,then会默认返回一个成功状态的promise,不过没有数据传递

这样我们就可以把2.1中handle函数改造下成返回一个promise,再次实现那个效果如下代码:

复制代码

// A任务start
handle('A')
  .then(() => {
    // B任务start
    return handle('B')
  })
  .then(() => {
    // C任务start
    return handle('C')
  })
  .then(() => {
    // D任务start
    return handle('D')
  })

复制代码

我们可以看到原来的4层回调嵌套被平铺开了,代码变得很清晰,我们一眼就能看到这段代码是要干什么,符合我们的正常思维。

---------- smile

参考: 

https://www.jianshu.com/p/063f7e490e9a

https://www.cnblogs.com/zhouli1980/p/3588605.html

https://blog.csdn.net/qq_32447301/article/details/83783853

你可能感兴趣的:(Vue,javascript,开发语言,ecmascript)