【JS】js中同步异步,Promise,Async/await, Observable

同步与异步

由于js是用来控制DOM的,所以js必须是单线程的,因为如果多个线程同时控制DOM操作,那么页面必然就乱套了。为了避免复杂性,所以它是单线程的。

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。

虽然js单线程的,但是浏览器内部不是单线程的,以此js才实现了异步。一些I/O操作、定时器的计时和事件监听(click, keydown…)等都是由浏览器提供的其他线程来完成的。

单线程的js执行任务的时候必须一个个来,任务过多就需要排队了,但是如果某个任务需要时间过长,一直等下去也不是个事,那么就有了 任务队列 ,js将任务划分为同步任务和异步任务,同步任务是在主进程中,等前一个任务执行完毕,再执行的任务,如:立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作。异步任务即,不会直接在主进程中执行,而是先进入任务队列,等待主进程中任务执行完毕再执行,如用户的点击事件、浏览器收到服务的响应和setTimeout插入的事件。

异步运行的过程如下:

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步

 

Callback:即回调函数,异步任务须指定回调函数,执行栈开始执行异步任务即执行异步任务中的回调函数。

 

Promise:

假设有多个需要串行执行的异步操作,容易产生回调地狱,并且错误处理也很复杂,所以有了Promise。

Promise就是能把原来的回调写法分离出来,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作,即在异步操作执行完后,用链式调用的方式执行回调函数。

Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。其核心思想一个Promise代表一个异步操作的结果,本质上是一个状态机,拥有三种状态:

pending 待定,一个Promise最初的状态。

fulfuilled 已实现 一个成功操作的Promise的状态

rejected 已拒绝 一个失败操作的Promise的状态

state_machine

如图所示,pending状态可以直接变为fulfilled或者rejected的状态,也可以通过resolve方法实现状态的变化。

Promise的简单实现要点:

 1. getThen(value)判断value是否是一个Promise对象,如果有则返回then方法,否则直接返回值,

2. doResolve(fn,onFulfilled,onRejected)方法,状态只能由初始状态变成成功或者失败状态,即onFulfilled()和onRejected()方法只能执行一次,即Promise的状态不能进行多次变化,

3. .done(onFulfilled,onRejected),保证异步操作

4..then(onFulfilled,onRejected),在方法内构造一个新的Promise,因为then方法会返回一个Promise,

以上为实现要点,具体实现参考:https://github.com/zxw018018/myBlog/issues/3

Promise包含多个方法。

Promise.resolve(value)

返回一个以给定值解析后的promise对象。如果传入的value本身就是promise对象,则该对象作为Promise.resolve方法的返回值返回;否则以该值为成功状态返回promise对象。

Promise.reject(reason):

返回一个带有拒绝原因reason参数的promise对象。

Promise.race(iterable):

返回一个promise,一旦迭代器中的某个promise解决或拒绝,返回的promise就会解决或拒绝。

Promise.all(iterable):

返回一个Promise实例,此实例在iterable参数内所有的promise都resolved或参数中不包含promise时 resolve;如果参数中promise有一个rejected,此实例reject,失败原因是第一个失败promise的结果。

catch 和 then 捕获错误:

  • catch写法是针对于整个链式写法的错误捕获的,而then第二个参数是针对于上一个返回Promise的。
  • 两者的优先级:就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是break, 可以继续执行后续操作不受影响。
  • 因为链式写法的错误处理具有“冒泡”特性,链式中任何一个环节出问题,都会被catch到,同时在某个环节后面的代码就不会执行了

 

有了promise后,除了广义的同步任务和异步任务,任务有了更精细的定义:

js中宏任务分为:整体代码script,setTimeout,setInterval,requestAnimationFrame,

微任务:MutationObserver,Promise.then catch finally

宏任务macrotask: 可以理解是每次执行栈执行的代码就是一个宏任务,整体script就是第一个宏任务。(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。主要场景有:主代码块、setTimeout、setInterval等
微任务microtask: 可以理解是在当前task执行结束后立即执行的任务。主要场景有:Promise、process.nextTick等。

1.主进程顺序执行任务,开始执行第一个宏任务,

2.遇到微任务就加到微任务队列中,遇到宏任务就加到宏任务队列中。

3.主进程中同步任务执行完毕,检查微任务队列是否为空,不为空则开始执行微任务。

4.微任务队列为空的时候开始执行宏任务队列。对于某个宏任务,如果其中有微任务和宏任务,则分别加到对应的队列中,然后该宏任务中所有的微任务执行完毕再执行宏任务队列中的下一个宏任务。

:在node中,会先将所有宏任务中的的同步任务执行完毕再执行各个宏任务中的微任务再执行各个宏任务中的宏任务。

node中宏任务:setTimeout,setInterval,setImmediate

微任务:process.nextTick,Promise.then catch finally

 

Promise相比于callback的优势,即控制反转带来带来的信任问题:

  • 调用过早:Promise可以保证一直是异步,而callback的中有可能是同步也可能是异步。

  • 调用过晚:callback会被被promise插队。

  • 调用回调次数太少或者太多:

    • callback调用了多次,Promise的状态只能被改变一次

    • callback未被调用,Promise可以添加Promise.Race()设置timeoutPromise,设置超时后的状态处理。

  • 没有把参数成功传递给毁掉函数:

    • promise会将resolve或者reject的值传给所有注册的回调函数,但是如果未将值传给resolve函数,和callback的不把值给毁掉函数一样。

  • 吞掉可能出现的错误或者异常

    • 回调函数中的抛出的异常,除非定义全局错误处理,否则无法在外部捕获。

    • Promise可以使用catch捕获

async/await

出现原因:如果有大量的异步请求的时候,流程复杂的情况下,使用Promise时,长长的的链式调用会阅读较为困难,而ES2017的Async/Await的出现就是为了解决这种复杂的情况。

基于Promise的,改良的,以同步的写法实现异步的功能,简化Promise的链式调用的繁琐写法。

async声明的函数的返回本质上是一个Promise。

注:async必须声明的是一个function,await就必须是在这个async声明的函数内部使用,必须是直系,不可以隔作用域链。

await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖

用await声明的Promise异步返回,必须“等待”到有返回值的时候,代码才继续执行下去。await在等待一个Promise的异步返回。当然也可以返回一个普通的值,但是就不是异步了。

 

RxJs

基于流的观察者模式和迭代器模式的一种库,是一种函数响应式编程。

  • 一个流就是一个不间断的按照时间排序的序列。它产生三种不同类型事件: 值,错误,完成的信号。对这三个定义事件处理函数,就可以异步的捕获这些事件

  • 每个stream有多个方法,调用时会基于原来的流返回一个新的流,原来的流不做修改,保证不可变性

  • 数据流支持链式调用,你可以组合不同的函数来处理流,创建和过滤不同的流。甚至一个流或多个流可以作为另外一个流的输入。你可以合并两个数据流。你可以过滤一个数据流,从中获取一个包含你感兴趣的事件的数据流。你可以将来自一个数据流的值映射到另外一个数据流

观察者模式

流(售房部/rx.js的Observable)是被观察的,某个函数订阅流的某个事件(推送房价),该函数是观察者(购房者/rx.js的Observer)。当流的某个事件产生了,对应的函数就会被执行。

迭代器模式

提供一种方法顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示。最常见的就是JavaScript 中像 Array、Set 等这些内置的可迭代类型,可以通过 iterator 方法来获取一个迭代对象,调用迭代对象的 next 方法将获取一个元素对象。

函数式编程:

  • 声明式: 与命令式相反,它描述目标的性质,让计算机明白目标,而非流程。
  • 纯函数:不受参数以外任何数据的影响,不会修改任何外部状态,如全局变量或者传入的参数对象
  • 数据不可变性:需要数据状态发生变化的时候不改变原有的数据,而是产生新的数据来表明这种变化。

响应式编程:

响应式编程是一种面向数据流和变化传播的编程范式。这意味着可以在编程语言中很方便地表达静态或动态的数据流,而相关的计算模型会自动将变化的值通过数据流进行传播。

RxJS中解决异步事件管理的基本概念如下:

  • Observable可观察对象:表示一个可调用的未来值或者事件的集合。
  • Observer观察者:一个回调函数集合,它知道怎样去监听被Observable发送的值
  • Subscription订阅: 表示一个可观察对象的执行,主要用于取消执行。
  • Operators操作符: 纯粹的函数,使得以函数编程的方式处理集合比如:map,filter,contact,flatmap
  • Subject(主题):等同于一个事件驱动器,是将一个值或者事件广播到多个观察者的唯一途径。
  • Schedulers(调度者): 用来控制并发,当计算发生的时候允许我们协调,比如setTimeout,requestAnimationFrame

Observable 被观察者,即值或者事件的流集合。

Subject 观察者的实现,继承Observable,同一个Observable可以被多个observer订阅。他们由Subject维护列表,Subject可以向多个Observer多路推送数值,是一类特殊的Observable。

  • 每一个Subject都是一个Observable,可以订阅他。从Observer的视角看,它并不能区分自己的执行环境是普通Observable的单路推送还是基于Subject的多路推送

  • 每一个Subject也可以是Observer,因为他同样由next、error、complete方法组成,调用next方法,Subject会给所有在他上面注册的Observer多路推送当前的值。

  • Subject是hot Observable,后面定义的并不会获取到订阅上一个订阅者之前收到的数据。

参考:

http://www.ruanyifeng.com/blog/2014/10/event-loop.html

https://github.com/zxw018018/myBlog/issues/3

https://juejin.im/post/59e85eebf265da430d571f89

https://segmentfault.com/a/1190000016788484?_ea=4854890#articleHeader12

https://baike.baidu.com/item/%E5%93%8D%E5%BA%94%E5%BC%8F%E7%BC%96%E7%A8%8B/15549849?fr=aladdin

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值