Promise就是一个对象,用于传递异步操作的消息.
Promise 是一个对象,也是一个构造函数
特点:
- Promise 实例有三种状态操作 Pending、Resolved(Fulfilled),Rejected
- 一旦改变,就不会再变,状态的改变只有可能是从 Pending 变为 Resolved 和从 Pending 变为 Rejected 能将异步操作以同步流程表达出来,避免出现callback hell
- 让回调函数变成了规范的链式写法,程序流程可以看得很清楚
缺点:
- 无法取消Promise,一旦新建就会立即执行的同步任务.
- 如果不设置callback,内部throw err
- 处于Pending状态时候,无法得知目前进展到那个阶段
示例1:
setTimeout(() => {
console.log("我是setTimeout代码块");
})
const p1 = new Promise((resolve, reject) => {
console.log("我是new Promise 的立即指向代码块");
})
示例2:
setTimeout(() => {
console.log("我是setTimeout代码块");
})
const p1 = new Promise((resolve, reject) => {
console.log("我是new Promise 的立即指向代码块");
setTimeout(() => {
console.log('异步指向任务完成!获得了一些结果')
resolve("成功了!");
},1000)
})
p1.then((res) => {
console.log(res);
}
)
Promise.prototype.then()
Promise 实例具有 then 方法,即 then 方法是定义在原型对象 Promise.prototype 上的.
作用是为 Promise 实例添加状态改变时的回调函数
then 方法可以接受两个回调函数作为参数,第二个参数就是可以选的.
then方法返回的是一个新的Promise实例(不是原来的那个Promise).因此可以采用链式写法,即then方法后面调用另一个then方法.采用链式的then,可以指定一组按照次序调用的回调函数,后面的回调函数,需等待该Promise对象状态的改变,才会被调用
Promise.prototype.cath()
一般来说,不要在then定义Reject状态的回调函数,即then的第二个参数,总是使用catch方法
Promise.prototype.cath方法是.then(null,rejection)的别名
当异步操作throw err时,状态会变成Rejected的,就会调用catch方法处理错误
Promise对象的错误具有”冒泡”性质,错误会被一直向后传递,直到下一个catch捕获,被捕获后不会传递到外层,不会有任何反应
JavaScript 是单线程语言,而现代游览器是支持多线程的,单线程从字面上就知道,他是容易导致进程阻塞的.
我们知道栈Stack,是一种后进先出(LIFO)的线性结构,是一个特殊的线性表.
队列Queue,是一种先进先去的线性结构(FIFO),是一个特殊的线性表.
Event LOOP 事件循环:
在计算机科学中,事件循环是一种编程构造或设计模式,用于在程序中等待和分发事件或消息。事件循环的工作原理是向某个内部或外部的“事件提供程序”发出请求(通常在事件到达之前阻止请求),然后调用相关的事件处理程序(“分派事件”)。事件循环有时也称为消息调度程序、消息循环、消息泵或运行循环
--wikipedia
JS采用事件循环Event LOOP的机制执行JS.
在了解此之前你所需的前置知识点:计算机系统
进程:是操纵系统对一个正常运行的程序的一种抽象.一个系统上可以同时运行多个进程
并发运行:一个进程的指令和另一个进程的指令是交错执行的,通过处理器在进程简切换,操纵系统这种交错执行的机制称为上下文切换.进程间的转换是由操纵系统的内核(kernel)管理,内核是系统管理全部进程所用代码和数据结构的集合
上下文:操纵系统保持跟踪进程运行所需的所有状态信息,这种状态,就是上下文,包括许多信息.比如PC和寄存器文件的当前值,以及贮存的内容.
线程:一个进程实际上可以由多个称为显参的执行单元组成.每个线程都运行在进程的上下文中,并共享同样的全局代码和全局数据.一般来说多线程比多进程更容易共享数据,因为线程一般比进程高效
深入理解计算机系统
语言的发展过程中,在ES6标准写进了Promise.
JS有两种
执行模式:
- 同步模式
- 异步模式
异步任务:
- 宏任务
- 微任务
宏任务:
是由宿主(浏览器、Node发起的),
宏任务为多线程异步逻辑,
微任务(Microtasks)
微任务是由JS自身发起的;
通常是由 promise 创建的,只有当 Javascript 调用栈为空,而控制权尚未返还给被 user agent 用来驱动脚本执行环境的事件循环之前,该微任务才会被执行。
起初微任务和任务之间的差异看起来不大。它们很相似;都由位于某个队列的 JavaScript 代码组成并在合适的时候运行。但是,只有在迭代开始时队列中存在的任务才会被事件循环一个接一个地运行,这和处理微任务队列是殊为不同的。
有两点关键的区别。
首先,每当一个任务存在,事件循环都会检查该任务是否正把控制权交给其他 JavaScript 代码。如若不然,事件循环就会运行微任务队列中的所有微任务。接下来微任务循环会在事件循环的每次迭代中被处理多次,包括处理完事件和其他回调之后。
其次,如果一个微任务通过调用
queueMicrotask()
, 向队列中加入了更多的微任务,则那些新加入的微任务 会早于下一个任务运行 。这是因为事件循环会持续调用微任务直至队列中没有留存的,即使是在有更多微任务持续被加入的情况下。注意: 因为微任务自身可以入列更多的微任务,且事件循环会持续处理微任务直至队列为空,那么就存在一种使得事件循环无尽处理微任务的真实风险。如何处理递归增加微任务是要谨慎而行的。
创建方式:
Macrotask宏任务:
- setTimeout
- setInterval
- MessageChannel
- IO
- UI交互事件
- setlmmediate(Node环境)
- script(整体代码块)
- postMessage
Microtask微任务:
- Promise.[ then/catch/finally ]
- process.nextTick(Node环境)
- MutationOberver(浏览器环境)
- Object.observe
- queueMicrotask
- requestAnimationFrame(有争议)
总结
更详细的事件循环算法(尽管与 规范 相比仍然是简化过的):
从 宏任务 队列(例如 “script”)中出队(dequeue)并执行最早的任务。
执行所有
微任务
- 当微任务队列非空时:
- 出队(dequeue)并执行最早的微任务。
- 当微任务队列非空时:
如果有变更,则将变更渲染出来。
如果宏任务队列为空,则休眠直到出现宏任务。
转到步骤 1。
安排(schedule)一个新的 宏任务:
- 使用零延迟的
setTimeout(f)
。
它可被用于将繁重的计算任务拆分成多个部分,以使浏览器能够对用户事件作出反应,并在任务的各部分之间显示任务进度。
此外,也被用于在事件处理程序中,将一个行为(action)安排(schedule)在事件被完全处理(冒泡完成)后。
安排一个新的 微任务:
- 使用
queueMicrotask(f)
。 - promise 处理程序也会通过微任务队列。
在微任务之间没有 UI 或网络事件的处理:它们一个立即接一个地执行。
所以,我们可以使用 queueMicrotask
来在保持环境状态一致的情况下,异步地执行一个函数。
参考:
ES6标准入门
深入计算机系统