onFulfilled 和 onRejected 必须作为函数调用
then() 也许会在同一个promise中调用多次
- 当promise变为完成态,所有相关的onFulfilled回调都被执行,且是按照回调函数注册的顺序执行
- 当promise变为拒绝态,所有相关的onRejected回调都按注册顺序执行
then必须返回一个promise: promise2 = promise1.then(onFulfilled, onRejected)
- 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒绝原因 reason
- 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
- 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的拒绝原因
- 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise
运行 [[Resolve]](promise, x) 需遵循以下步骤:
x 与 promise 相等
如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
x 为 Promise
如果 x 为 Promise ,则使 promise 接受 x 的状态 注4:
如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
如果 x 处于执行态,用相同的值执行 promise
如果 x 处于拒绝态,用相同的据因拒绝 promise
x 为对象或函数
如果 x 为对象或者函数:
把 x.then 赋值给 then
如果取 x.then 的值时抛出错误 e ,则以 e 为拒绝原因拒绝 promise
如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
如果调用 then 方法抛出了异常 e:
如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
否则以 e 为据因拒绝 promise
如果 then 不是函数,以 x 为参数执行 promise
如果 x 不为对象或者函数,以 x 为参数执行 promise
如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise
------------------------------------------------------------------------
promise原理
观察者模式 + 事件循环 = 异步模式
观察者模式的核心:订阅和发布
什么时候订阅? 事件循环遍历微任务的时侯订阅
什么时候发布? 当满足一定条件时发布。这个由开发者来控制 而且promise的实现中保证了发布晚于订阅
new Promise(function(resolve, reject) {
resolve(1)
}).then(value => console.log(value))
比如这个例子,如果 resolve()里面没有一个保证(比如 setTimeout()) 那么将then里面的回调将不会得到通
知 resolve()就是用来通知回调函数的
如请求数据,当请求完后,resolve()一下【宏任务】 then()时注册的回调即被调用
前面说过微任务的遍历在宏任务的遍历之前
函数调用栈与任务队列
Javascript有一个main thread 主进程和call-stack (一个调用堆栈),在对一个调用堆栈中的task处理的时候,其他的都要等着。当在执行过程中遇到一些类似于setTimeout等异步操作的时候,会交给浏览器的其他模块 (以webkit为例,是webcore模块) 进行处理,当到达setTimeout指定的延时执行的时间之后,task (回调函数)会放入到任务队列之中。一般不同的异步任务的回调函数会放入不同的任务队列之中。等到调用栈中所有task执行完毕之后,接着去执行任务队列之中的task (回调函数)。
调用栈中遇到DOM操作、ajax请求以及setTimeout等WebAPIs的时候就会交给浏览器内核的其他模块进行处理,webkit内核在Javasctipt执行引擎之外,有一个重要的模块是webcore模块。webcore分别提供了DOM Binding、network、timer模块来处理底层实现。等到这些模块处理完这些操作的时候将回调函数放入任务队列中,之后等栈中的task执行完之后再去执行任务队列之中的回调函数。
当遇到setTimeout的时候,执行引擎将其添加到栈中。但发现这是异步事件,立刻弹出,让相应模块处理,处理完后,去对应的任务队列注册。【注意,这里什么时候注册回调是不确定的,比如setTimeout(()=> {console.log(1), 10000})】 10秒后 相应模块处理完后,去宏任务队列注册回调
任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。
macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)
Promise构造函数中的第一个参数,是在new的时候执行,因此不会进入任何其他的队列,而是直接在当前任务直接执行了,而后续的.then则会被分发到micro-task的Promise队列中去。
----------------------------------------------------------------
function myPromise(exec) {
this.onfulfilled_cb = []
var resolve = function() {
setTimeout(() => {
// 改变promise状态
...
// 获取终值
...
// 执行回调
this.onfulfilled_cb.forEach((cb, i) => {
cb(终值)
})
})
}
var reject = function() {...}
...
try {
exec(resolve, reject)
}catch (e){
reject(e)
}
}
// 微任务里面注册回调
myPromise.prototype.then(onFulfilled, onRejected) {
return new myPromise((resolve, reject) => {
// 注册成功回调
this.onfulfilled_cb.push((value) => {
try{
onFulfilled(value)
}
})
// 注册失败回调
...
})
}
示例分析
new myPromise(function(resolve, reject) {
resolve(1)
}).then(value => console.log(value))
上面代码中的 resolve()里面的内容在setTimeout里面 交给js引擎外的webcore模块 当webcore模块到指定时间后(如果没有指定时间,将取决于其他消息),将会去宏任务队列注册。
遇到 then() 方法进入了微任务队列 【如果微任务很久都没执行完,也就是很久才去微任务队列注册,而假设此时宏任务已经完成了注册,事件循环发现微任务队列还没有任务,事件循环发现宏任务队列有任务,拿出执行栈执行,但这不可能】实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
function myPromise(constructor){
let self=this;
self.status="pending" //定义状态改变前的初始状态
self.value=undefined;//定义状态为resolved的时候的状态
self.reason=undefined;//定义状态为rejected的时候的状态
function resolve(value){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//两个==="pending",保证了状态的改变是不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕获构造异常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason);
break;
default:
break;
}
}
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//输出1
然鹅一旦修改下
var p=new myPromise(function(resolve,reject){setTimeout(() => {resolve(1)})});
p.then(function(x){console.log(x)})
这种情况就不行了
因为resolve没有及时发出信号。导致then注册的回调无法执行
// promise 采用了观察者模式,用特定方式 注册 对应 状态 的事件处理函数
//
// 定义promise 状态
const pending = "pending"
const fulfilled = "fulfilled"
const rejected = "rejected"
// promise 是一个对象或函数 它有一个方法then()
function myPromise(fn) {
if(typeof fn != "function") return
this._state = pending // 初始状态为等待态
this._value = undefined // 保存执行态的value
this._reason = undefined // 拒绝态的reason
this.onfulfilled_cb = [] // 多次调用 有多个onFulfilled
this.onrejected_cb = [] // 多次调用 有多个onRejected
// 处于等待态时,promise 需满足以下条件:可以迁移至执行态或拒绝态
// promise成功时的操作 如改变状态 回调执行 promise处于执行态时 有一个不可变的终值
this.resolve = function(value) {
// value 是成功时接收到的终值
// 从pending 变为 fulfilled
// 处于执行态时不能迁移至其他任何状态 处于拒绝态时不能迁移至其他任何状态
// 保证在then() 成功执行后的那轮事件循环之后的新的执行栈中执行
// 调用 resolve() 但这个是宏任务 进入宏任务队列
// 然后.then() 进入微任务队列 同步代码执行完毕 先去微任务队列查看
// 在微任务中添加回调至回调数组 微任务完毕 再去查看宏任务队列
// 遍历回调数组 调用微任务队列时添加的回调
// 如此一来实现了观察者模式 利用了事件循环的原理完成了异步编程
setTimeout(() => {
if(this._state != pending) return
this._state = fulfilled
this._value = value
this.onfulfilled_cb.forEach((cb, i) => {
cb(this._value)
})
})
}
//
this.reject = function(reason) {
setTimeout(() => {
if(this._state === "pending") {
this._state = rejected
this._reason = reason
this.onrejected_cb.forEach((cb, i) => {
cb(reason)
})
}
})
}
// promise 不会处理任务 它只是协调
// then() 方法属于微任务 就像setTimeout属于宏任务 都有相应模块处理
this.then = function(onFulfilled, onRejected) {
// 如果onFulfilled不是函数 必须被忽略
if(typeof onFulfilled != "function") return
// 如果 onRejected 不是函数,其必须被忽略
if(typeof onRejected != "function") return
let newPromise
// 刚进来一开始肯定是pending
// promise的原理是观察者模式 在then()方法中收集回调
// 当resolve时 或 reject时 调用刚才收集的回调
if(this._state === pending) {
// then 方法必须返回一个 promise 对象
return newPromise = new myPromise((resolve, reject) => {
// 在新的promise中的fn先暂存回调
this.onfulfilled_cb.push((value) => {
// 将onFulfilled保存到数组
try{
// 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程
// Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值
let x = onFulfilled(value)
// promise解决过程封装
// 闭包 newPromise
resolvePromise(newPromise, x, resolve, reject)
} catch(e) {
reject(e)
}
})
this.onrejected_cb.push((reason) => {
try {
let x = onRejected(reason)
resolvePromise(newPromise, x, resolve, reject)
} catch(e) {
reject(e)
}
})
})
}
// 如果是同一个promise重复调用then() 重复调用意味着 _state 是 fulfilled 或 rejected
if(this._state === fulfilled) {
return newPromise = new myPromise((resolve, reject) => {
// 为了确保回调函数在then()之后执行
setTimeout(() => {
try {
let x = onFulfilled(value)
resolvePromise(newPromise, x, resolve, reject)
}catch (e) {
reject(e)
}
})
})
}
}
try
fn(resolve, reject)
} catch(e) {
reject(e)
}
}
// 递归实现then的链式调用
function resolvePromise(newPromise, x, resolve, reject) {
// 1. x 和 promise 指向同一对象 以 TypeError 为拒绝原因拒绝执行 promise
if(x === newPromise) return reject(new TypeError('循环引用'))
let called = false // 记录是否已经调用 promise的状态只能更改一次
// 2. x 为 promise 则使promise接收x的状态
if(x instanceof myPromise) {
// 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
if(x._state == pending) {
x.then(y => {
resolvePromise(newPromise, y, resolve, reject)
}, reason => {
reject(reason)
})
}
// 如果 x 处于执行态,用相同的值执行 promise
else {
x.then(resolve, reject)
}
}else if(x != null && typeof x === "object" || typeof x === "function") {
// 如果 x 为对象或者函数
try {
let then;
// 把 x.then 赋值给 then
then = x.then;
if(typeof then === "function") {
// 如果 then 是函数,将 x 作为函数的作用域 this 调用之
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,
// 第二个参数叫做 rejectPromise:
// 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,
// 则优先采用首次调用并忽略剩下的调用 使用called
then.call(x, y => {
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
if(called) return
called = true
resolvePromise(newPromise, y, resolve, reject)
}, reason => {
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
if(called) return
called = true
reject(reason)
})
}else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x)
}
}
catch(e) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
if(called) return
called = true
return reject(e)
}
}else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x)
}
}
手写promise(二)
最新推荐文章于 2022-04-28 08:00:00 发布