JS 线程是单线程运行机制, 就是自己按顺序做自己的事, 浏览器线程用于交互和控制, JS 可以操作 DOM 元素,
说起 JS 中的异步时, 我们需要注意的是, JS 中其实有两种异步, 一种是基于浏览器的异步 IO, 比如 Ajax, 另外一种是基于计时方法 setTimeout 和 setInterval 的异步
对于异步 IO, 比如 ajax, 写代码的时候都是顺序执行的, 但是在真正处理请求的时候, 有一个单独的浏览器线程来处理, 并且在处理完成后会触发回调这种情况下, 参与异步过程的其实有 2 个线程主体, 一个是 javascript 的主线程, 另一个是浏览器线程
熟悉 Javascript 的人都知道, Javascript 内部有一个事件队列, 所有的处理方法都在这个队列中, Javascript 主线程就是轮询这个队列处理, 这个好处是使得 CPU 永远是忙碌状态这个机制和 Windows 中的消息泵是一样的, 都是靠消息 (事件) 驱动,
对于 setTimeout 和 setInterval 来说, 当 js 线程执行到该代码片段时, js 主线程会根据所设定的时间, 当设定的时间到期时, 将设置的回调函数放到事件队列中, 然后 js 主线程继续去执行下边的代码, 当 js 线程执行完主线程上的代码之后, 会去循环执行事件队列中的函数至于 setTimeout 或者 setInterval 设定的执行时间在实际表现时会有一些偏差, 普遍的一个解释为, 当定时任务的时间到期时, 本应该去执行该回调函数, 但是这时 js 主线程可能还有任务在执行, 或者是该回调函数再事件队列中排的比较靠后, 就导致该回调函数执行时间与定时器设定时间不一致
那么问题来了, 什么是事件队列?
eventloop 是一个用作队列的数组, eventloop 是一个一直在循环执行的, 循环的每一轮成为一个 tick, 在每一个 tick 中, 如果队列中有等待事件, 那么就会从队列中摘取下一个事件进行执行, 这些事件就是我们之前的回调函数现在 ES6 精确指定了事件循环的工作细节, 这意味着在技术上将其纳入了 JavaScript 引擎的势力范围, 而不只是由宿主环境决定了, 主要的一个原因是 ES6 中 promise 的引入
- var eventloop = []
- var event;
- while(true){
- if(eventloop.length>0){
- // 拿到队列中的下一个事件
- event = eventloop.shift();
- // 现在执行下一个事件
- try{
- event();
- }catch(e){
- reportError(e);
- }
- }
- }
在浏览器端, setTimeout 中的最小时间间隔是 W3C 在 html 标准中规定, 规定要求低于 4ms 的时间间隔算为 4ms
任何时候, 只要把一个代码包装成一个函数, 并指定它在响应某个事件时执行, 你就是在代码中创建了一个将来执行的模块, 也由此在这个程序中引入了异步机制
js 引擎并不是独立运行的, 它运行在宿主环境中, 就是我们所看到的 web 浏览器, 当然, 随着 js 的发展, 包括最近的 Node, 便是给 js 提供了一个在服务器端运行的环境并且现在的 js 还嵌入到了机器人到电灯泡的各种各样的设配中
但是这些所有的环境都有一个共同的点, 即为都提供了一种机制来处理程序中的多个块的执行, 且执行每个块时调用 JavaScript 引擎, 这种机制被称为事件循环
js 引擎本身并没有时间的概念, 只是一个按需要执行 JavaScript 任意代码片段的环境
对于 js 中的回调, 我们最常见的就是链式回调和嵌套回调
我们经常再 ajax 中嵌套 ajax 调用然后再嵌套 ajax 调用, 这就是回调地狱, 回调最大的问题就是控制反转, 它会导致信任链的完全断裂, 为了解决回调中的控制反转问题, 有些 API 提供了分离回调 (一个用于成功通知, 一个用于失败通知), 例如 ajax 中的 success 函数和 failure 函数, 这种情况下, API 的出错处理函数 failure() 常常是可以省略的, 如果没有提供的话, 就是假定这个错误可以吞掉
还有一种回调模式叫做 error-first" 风格, 其中回调的第一个参数保留用作错误对象, 如果成功的话, 这个参数就会被清空 / 置假
回调函数是 JavaScript 异步的基础单元, 但是随着 JavaScript 越来越成熟, 对于异步领域的发展, 回调已经不够用了
Promise
Promise 是异步编程中的一种解决方案, 最早由社区提出和实现, ES6 将其写进了语言标准, 统一了用法, 原生提供了 Promise 对象
Promise 是一种封装和组合未来值的易于复用的机制一种在异步任务中作为两个或更多步骤的流程控制机制, 时序上的 this-then-that. 假定调用一个函数 foo(), 我们并不需要去关心 foo 中的更多细节, 这个函数可能立即完成任务, 也可能过一段时间才去完成对于我们来讲, 我们只需要知道 foo()什么时候完成任务, 这样我们就可以去继续执行下一个任务了, 在传统的方法中, 我们回去选择监听这个函数的完成度, 当它完成时, 通过回调函数通知我们, 这时候通知我们就是执行 foo 中的回调, 但是使用 promise 时, 我们要做的是侦听来自 foo 的事件, 然后在得到通知的时候, 根据情况而定
其中一个重要的好出就是, 我们可以把这个事件中的侦听对象提供给代码中多个独立的部分, 在 foo()完成的时候, 他们都可以独立的得到通知:
- var evt = foo();
- // 让 bar()侦听 foo()的完成
- bar(evt);
- // 让 baz()侦听 foo()的完成
- baz(evt);
上边的例子中, bar 和 baz 中不需要去知道或者关注 foo 中的实现细节而且 foo 也不需要去关注 baz 和 bar 中的实现细节
同样的道理, 在 promise 中, 前面的代码片段会让 foo()创建并返回一个 Promise 实例, 而且在这个 Promise 会被传递到 bar()和 baz()中所以本质上, promise 就是某个函数返回的对象你可以把回调函数绑定再这个对象上, 而不是把回调函数当成参数传进函数
- const promise = doSomething();
- promsie.then(successCallback,failureCallback){
- }
当然啦, promise 不像旧式函数将回调函数传递到两个处理函数中, 而且会有一个优点:
在 JavaScript 事件队列的本次 tick 运行完成之前, 回调函数永远不会执行
通过. then 形式添加的回调函数, 甚至都在异步操作完成之后才被添加的函数, 都会被调用
通过多次调用. then, 可以添加多个回调函数, 他们会按照插入顺序并且独立运行
但是, Promise 最直接的好出就是链式调用
- doSomething().then(function(result) {
- return doSomethingElse(result);
- })
- .then(function(newResult) {
- return doThirdThing(newResult);
- })
- .then(function(finalResult) {
- console.log('Got the final result:' + finalResult);
- })
- .catch(failureCallback);
并且在一个失败操作之后还可以继续使用链式操作, 即使链式中的一个动作失败之后还能有助于新的动作继续完成
在调用 Promise 中的 resolve()和 reject()函数时如果带有参数, 那么他们的参数会被传递给回调函数
Promise.resolve()和 Promise.reject()是手动创建一个已经 resolve 或者 reject 的 promise 快捷方法通常, 我们可以使用 Promise.resolve()去链式调用一个由异步函数组成的数组例如:
Promise.resolve().then(func1).then(func2);
Promise.all()和 Promiserace()是并行运行异步操作的两个组合式工具
Promise.then()方法用来分别指定 resolved 状态和 rejected 状态的回调函数传递到 then 中的函数被置入了一个微任务队列, 而不是立即执行, 这意味着它是在 JavaScript 事件队列的所有运行结束了, 事件队列被清空之后才开始执行
- let promise = new Promise(function(resolve, reject) {
- console.log('Promise');
- resolve();
- });
- promise.then(function() {
- console.log('resolved.');
- });
- console.log('Hi!');
- // Promise
- // Hi!
- // resolved
Promise.then()方法返回一个 Promise, 它最多需要有两个参数: Promise 的成功和失败情况的回调函数
- p.then(onFulfilled, onRejected);
- p.then(function(value) {
- // fulfillment
- }, function(reason) {
- // rejection
- });
onFulfilled: 当 Promise 变成接受状态 (fulfillment) 时, 该参数作为回调函数被调用该函数有一个参数, 即接受的值
onRejected: 当 Promise 变成拒绝状态时, 该参数作为回调函数被调用该函数有一个参数, 即拒绝的原因
Promise 的状态一旦改变, 就永久保持该状态, 不会再改变了
Promise 中的错误处理
一般的情况, 我们会在每次的 Promise 中抛出错误, 在 Promise 中的 then 函数中的 rejected 处理函数会被调用, 这是我们作为错误处理的常用方法:
- let p = new Promise(function(resolve,reject){
- reject('error');
- });
- p.then(function(value){
- success(value);
- },function(error){
- error(error)
- }
- )
但是一种更好的方式是使用 catch 函数, 这样可以处理 Promise 内部发生的错误, catch 方法返回的还是一个 Promise 对象, 后边还可以接着调用 then 方法而且 catch 方法尽量写在链式调用的最后一个, 避免后边的 then 方法的错误无法捕获
- let p = new Promise(function(resolve,reject){
- reject('error');
- });
- p.then(function(value){
- success(value);
- }).catch(function(error){
- console.log('error');
- }}
Promise.finally()函数, 该方法是 ES2018 引入标准的指定不管 Promise 对象最后状态如何, 都会执行的操作 finally 方法的回调函数不接受任何参数, 这意味着没有办法知道, 前面的 Promise 状态到底是 fulfilled 还是 rejected 这标明, finally 方法里面的操作, 是与状态无关的, 不依赖于 Promise 的执行结果
上述文章, 如有错误, 还请指正, 谢谢!!!
来源: https://www.cnblogs.com/blackgan/p/8617850.html