那js为什么不设计成多线程?

为什么需要异步?

因为js是单线程的编程语言,同一时间只执行一个任务,当有一段耗时较长的计算代码或ajax请求出现,会出现用户等待时间过长的情况,此时当前任务还未完成,导致其他的操作也都会滞留,这是低效率的。

那js为什么不设计成多线程?

这就和创造js这门编程语言有关了。JS最初是作为一种用于网页前端交互的脚本语言而设计的,它的主要任务是操作DOM、响应用户交互等。在早期,JS并没有涉及到需要处理大量并发任务的场景,因此为了语言的轻量和简单,单线程设计是合理的选择。

多线程编程很容易导致共享资源的竞争和数据同步问题,这会增加代码的复杂性和出错的可能性,而单线程模型则简化了这些问题。比如JS没有多线程语言中锁、解锁的过程,这样节约上下文切换的时间。

异步是什么?

异步是一种编程模式,它允许程序在执行某个任务的同时,不必等待该任务完成就能继续执行其他任务。比如:

 

javascript

复制代码

setTimeout(function foo(){ console.log('这就是异步!'); }, 1000); console.log('异步是什么');

这里的setTimeout就是一个异步任务,由于 JS 是单线程执行的,当遇到 setTimeout 这样的异步操作时,它会被放到事件队列中,等待当前执行栈清空后才会执行。因此,console.log('异步是什么'); 会立即执行并输出 '异步是什么',而 function foo(){...} 则会在 1 秒后执行,输出 '这就是异步'。所以JS才不会和傻子一样等1000ms执行回调函数,而是先执行之后的代码。

异步的发展史👏

1. 回调函数callback

回调函数是一种常见的处理异步操作。它实质上是一个函数,作为参数传递给另一个函数,并在特定事件发生或异步操作完成后被调用。

比如我有个同步操作(foo)和一个异步操作(bar)

 

scss

复制代码

let count = 0 function foo() { console.log(count); } function bar () { setTimeout(() => { count = 1 },1000) } bar() foo() // 输出:0

按照JS单线程来说,同步操作一定在异步之前,这样代码只会输出0,而我们想将count = 1console.log(count);之前执行能怎么样呢?

 

scss

复制代码

let count = 0 function foo() { console.log(count); } function bar (callBack) { setTimeout(() => { count = 1 callBack() },1000) } bar(foo) // 输出:1

我们可以采用回调的方式处理,在函数 bar()中,它接受一个回调函数 callBack 作为参数。在1000ms后,会将 count 的值设为 1,然后调用传递进来的回调函数 callBack(),此时的callBack()也正是foo()

综合起来,当程序执行到 bar(foo) 时,它会等待1秒,然后将 count 的值修改为 1,并调用 foo() 函数。此时 foo() 函数会输出 count 的值,即 1

优点:
  • 解决了同步问题
缺点:
  • 回调地狱(多层级嵌套)
  • 不能捕获错误
  • 嵌套多层之后,代码可读性差

2. Promise

在ES6中,官方打造了Promise来解决异步、回调地狱等问题。它能更加优雅地书写复杂的异步任务

Promise对象具有这些特点:

  • 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

    这两个特点取自阮一峰ES6入门(es6.ruanyifeng.com/#docs/promi…)

场景

 

javascript

复制代码

function a () { return new Promise((resolve, reject) => { setTimeout(() => { console.log('a'); resolve('ok') },1000) }) } function b () { setTimeout(() => { console.log('b'); },500) } a().then( (res) => { // res = 'ok console.log(res); b() }, (err) => { console.log(err); } ) //输出:a ok b

从场景中出现的new,不难看出Promise对象其实就是一个构造函数,是用来生成一个Promise实例对象的,构造函数中接受一个回调函数作为参数,该回调函数中也有两个参数resolvereject注意:这两个参数也是两个函数!

Promise实例生成以后,可以用then方法分别指定resolved状态rejected状态的回调函数。

优点:
  • 通过链式调用,避免了深度嵌套的回调函数,使得代码更加清晰易读。
  • Promise实例之间可以轻松组合和复用,使得代码更加模块化和灵活。
  • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
缺点:
  • Promise对象一旦创建后,其状态就不可再改变。这意味着无法取消或者终止Promise实例的执行。
  • 虽然Promise内置了错误处理机制,但有时错误处理可能不够直观,特别是在处理多个并发Promise时,可能会出现不易定位和调试的问题。
  • 当处于 pending 状态时,无法得知目前进展到哪一个阶段

3. Generator

Generator是 ES6 中引入的一种特殊的函数类型,它可以在执行过程中暂停并且可以从暂停的位置恢复执行。Generator 函数通过使用 function* 关键字来定义,其中可以包含零个或多个 yield 关键字,用于暂停函数的执行并向调用者返回一个值。

场景

 

javascript

复制代码

function* g() { var o = 1 yield o++ yield o++ yield o++ } let gen = g() // 迭代对象 console.log(gen.next()); // { value: 1, done: false } console.log(gen.next()); // { value: 2, done: false } console.log(gen.next()); // { value: 3, done: false } console.log(gen.next()); // { value: undefined, done: true }

Generator 函数在调用时并不立即执行,而是返回一个迭代器对象,该迭代器对象包含了内部状态的指针,用于控制 Generator 函数的执行。 下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。

优点

  • 可以分段执行,可以暂停
  • 可以控制每个阶段的返回值
  • 可以知道是否执行完毕
  • 帮助打造了async await

缺点

  • Generator函数的执行流程更加复杂,需要手动调用next()方法来控制执行流程,会让代码不够直观、显得繁琐。
  • 最好借助 Thunk 和 co 模块 处理异步

4. async/await

async/await 是 ES7中引入的异步编程解决方案,它是基于 Promise 的语法糖。

async/await 和 promise的关系

  • async/await可以替代Promise链式调用(.then())的方式,使得异步操作的代码更加清晰和易读。
  • async/await结合try-catch语句,代替了 Promise的 catch。与Promise链式调用相比,错误处理更加直观和统一。
  • 执行 async 函数,返回的是 Promsie 对象

场景

 

javascript

复制代码

function foo(num){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(num*10) },1000) }) } function bar(num){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(num*100) },2000) }) } async function test(){ let res1 = await foo(1) let res2 = await bar(10) console.log(res1,res2); } test() // 输出:10 1000

这个场景中foo函数放回一个Promise对象,在指定的时间后解析Promise,并返回传入的参数 num 乘以 10 的结果,bar函数同理。test函数在执行时使用 await 关键字等待 foobar 函数的返回结果。由于 await 关键字会使异步操作变为同步执行,所以耗时为3000ms。

从这个场景我们可以得出

  • async表示这是一个async函数, await只能用在async函数里面,不能单独使用
  • async返回的是一个Promise对象
  • await等待的是一个Promise对象,后面必须跟一个Promise对象
  • await Ywis相当于是 Ywis.then,并且只是成功态的then

优点

  • async/await 更加直观和易读。它让异步操作的代码看起来更像是同步的,使得代码更加清晰和易于理解。
  • async/await是由 promise + generator来实现的,本质是在generator的基础上通过递归的方式来自动执行一个又一个的next函数,当done为true时结束递归。
  • async/await 是建立在Promise之上的,它使用Promise来管理异步操作。
  • await 关键字会等待其后的异步操作完成后再继续执行后续的代码。

缺点

  • 在使用多个await时,异步操作会变为串行执行,这可能导致性能瓶颈。
  • 没有错误捕获机制

结尾

所以JS的异步发展史,可以认为是从 callback -> promise -> generator -> async/await

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值