MutationObserver、Promise.then()或reject()、Promise为基础开发的其它技术,比如fetch API、V8的垃圾回收过程、Node独有的process.nextTick。
script、setTimeout、setInterval 、setImmediate 、I/O 、UI rendering。
javascript 中事件循环(event loop)机制:
将整个脚本作为一个宏任务进行执行。
一般来说按照从上到下的执行顺序,执行过程中解析到同步代码直接运行,解析到宏任务则放入宏任务队列,解析到微任务则放入到微任务队列。
当前宏任务执行完成后出队列,检查微任务队列,如果有微任务则依次执行完毕。
执行浏览器的UI线程进行渲染操作。
检查是否有Web Worker任务,如果有则执行。
执行完成本轮的宏任务后,回到第2步,依次循环执行,直到宏任务和微任务队列都为空为止。
通过一段简单的代码来实现一下 发布订阅者模式。
let dep= {
list:[],
on:function(fn){
list.push(fn)
},
emit:function(){
this.list.forEach(event=>{
typeof event==='function'?event() : null
})
}
}
以上代码实现了一个简单的发布订阅者模式,发布者存在一个数组list用于登记订阅者(异步执行函数),当某个时候执行了 emit,订阅的异步函数都会执行。例如订阅报纸的读者首先需要在报社登记自己的联系信息,当报社里面发版新的报纸时会通知在报社登记了信息的读者,然后他们就可以去报社或者收到新的报纸。
问:promise是在什么场景下被使用?
答:它为了解决 “函数的回调地狱”
举个简单的例子:
firstCallback(value1=>{
// 执行value1所在区域内的代码
secondCallback(value2=>{
// 执行value2所在区域内的代码
thirdCallback(value3=>{
// 执行value3所在区域内的代码
...
})
})
})
上面的这个例子中我们可以发现,每次增加了一个异步请求,那么就会多嵌套一层回调函数,过多的异步请求则会产生“回调地狱”这个问题,极其不易阅读和理解。
使用promise后重新改造代码:
firstCallback()
.then(value1 => {
// 执行value1所在区域内的代码
return secondCallback()
})
.then(value2 =>{
// 执行value2所在区域内的代码
return thirdCallback()
})
.then((value3) => {
// 执行value3所在区域内的代码
})
由此可见,使用了promise改造后的代码清晰易懂,解决了“回调地狱”的问题。
promise的核心原理就是发布订阅者模式,创建两个队列分别保存回调函数的两个状态:成功(onResolve) 和 失败(onReject)。
pending(正在等待):等待状态,比如正在进行网络请求xhr或者定时器setTimeout没有到时间。
fulfilled(已成功):成功状态,promise 执行了 resolve时,处于该状态,并且调用 then()。then()中也可以接收失败回调。
rejected(已失败):失败状态,promise 实行了 reject时,处于该状态,并且调用 catch()。
Promise对象的状态改变只有两种:从pending变为 fulfilled 和 从pending变为rejected,一旦确定就不会再改变(不可逆)。
new promise 时需要传递一个 executor 执行器,执行器会立即执行。
执行器中包含了两个参数:成功(onResolve) 的函数 和 失败(onReject)的函数,它们调用时可以接收任意值value。
promise最初为pending(等待)状态,pending只能转为 fulfilled状态 或者 rejected状态,再执行相应队列中的任务。
promise的then方法中可以传递两个参数,一个是成功的回调onfulfilled 和 失败的回调onrejected。
promise实例中如果状态是fulfilled 则执行then方法中的onfulfilled 回调函数,如果状态是rejected则执行then方法中的onrejected回调函数。
promise 中同一个实例可以调用多次then方法,判断promise的状态,如果是pending状态,就需要将回调函数onfulfilled和onrejected存储起来,等到promise的状态确认为fulfilled或者rejected才执行。
promise 构造函数接收一个函数作为参数,函数中的两个参数分别为 resolve回调函数 和 reject回调函数。
let myPromise = newPromise((resolve,reject)=>{
if(true){
//如果状态为true 则promise 执行resolve,状态从 pending -> fulfilled.
resolve(value)
}else{
//如果状态为false 则promise 执行reject,状态从 pending -> rejected.
reject(error)
}
})
resolve 函数的作用:将Promise实例对象的状态从"等待"转到"成功",并将异步操作的结果作为参数传入相应的成功回调函数执行。
reject 函数的作用:将Promise实例对象的状态从"等待"转到"失败",并将异步操作的结果作为参数传入相应的失败回调函数执行。
promise
.then(value=> {
//执行成功的回调函数
console.log(value)
})
.catch(error=> {
//执行失败的回调函数
console.log(error)
})
//封装一个 自定义promise函数
functionmyPromise(flag,data,error){
returnnewPromise((resolve, reject) => {
if (flag) {
//flag 为true 则执行 resolve()
resolve(data)
} else {
// flag 为false 则执行 reject()
reject(err)
}
});
}
//创建实例对象
const promise1 = newmyPromise(true,1,'失败了')
const promise2 = newmyPromise(true,2,'失败了')
const promise3 = newmyPromise(false,3,'失败了')
promise1
.then((value)=>{
console.log(value)
return promise2
},(error)=>{
console.log(error)
})
//执行 promise2 的 回调
.then((value)=>{
console.log(value)
return promise3
},(error)=>{
console.log(error)
})
//执行 promise3 的 回调
.then((value)=>{
console.log(value)
},(error)=>{
console.log(error)
})
// 答:1 2 操作失败
// 使用promise 封装一个myAjax函数对象。
let myAjax = function(path,params,method){
$.ajax({
url:path,
type:method,
data:params,
dataType:'json',
success(res){
resolve(res)
},
error(error){
reject('出错了')
}
})
}
//执行 myAjax 函数
myAjax('xxx',{value:20},'get').then(res=>{
console.log(res)
})
Promise.resolve方法的作用是可以把有需要的对象转为Promise对象。
resolve()是用来表示promise的状态为fullfilled,相当于只是定义了一个有状态的Promise,但是并没有调用它;
只有promise调用then的时候,then里面的函数才会被推入微任务中。
Promise.resolve方法的参数分成四种情况:
1.参数是一个 Promise 实例
如果参数是Promise实例,则Promise.resolve()不会进行任何处理,直接返回这个实例。
2.参数是一个thenable对象
thenable对象指的是一个具有then方法的对象,Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
let thenable= {
then: function(resolve, reject) {
resolve(42);
}
};
let p1 = Promise.resolve(thenable);
// thenable对象的then方法执行后,对象p1的状态从pending变为fulfilled
p1.then((value)=> {
console.log(value); // 42
});
3.参数不具有then方法的对象
如果参数是一个原始值或者是一个不具有then方法的对象,执行Promise.resolve会返回一个新的Promise对象,状态转为fulfilled。
const p = Promise.resolve('HelloWorld');
// HelloWorld 字符串被转换成 Promise对象,状态改变为 fulfilled。
p.then((s)=>{
console.log(s)
});
// 答:HelloWorld
4.不带有任何参数
Promise.resolve方法调用时如果不传递任何参数,则直接返回一个fulfilled状态的 Promise对象。
setTimeout(function () {
console.log('1');
}, 0);
Promise.resolve().then(function () {
console.log('2');
});
console.log('3');
// 答:3 2 1
Promise.resolve().then(() =>console.log(2)).then(() =>console.log(3));
console.log(1);
//答: 1, 2, 3
Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) =>reject('出错了'))
// 生成一个 Promise 对象的实例p,状态为rejected,回调函数会立即执行
p.then(null, function (s) {
console.log(s)
});
// 出错了
Promise.reject()方法的参数,会原封不动地传给onRejected回调函数作为参数。
Promise.all用于将多个Promise实例包装成一个新的Promise实例,接收一个数组作为参数,也可以不是数组但是必须具备 iterator 接口。
const myAllPromise = Promise.all([myPromise1, myPromise2, myPromise3])
分析:myAllPromise 的状态 由myPromise1、myPromise2、myPromise3来决定,有以下两种情况:
情况一:myPromise1、myPromise2、myPromise3的状态都为fulfilled,则myAllPromise 的状态变为fulfilled,此时myPromise1、myPromise2、myPromise3的返回值组成一个数组,传递给myAllPromise 的回调函数。
情况二:myPromise1、myPromise2、myPromise3的状态只要有一个为 rejected,则myAllPromise 的状态变为rejected,三个实例中第一个返回rejected的实例的返回值会传递给myAllPromise 的回调函数。
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result=>result)
.catch(e=>e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result=>result)
.catch(e=>e);
Promise.all([p1, p2])
.then(result=>console.log(result))
.catch(e=>console.log(e));
//答: ["hello", Error: 报错了]
解析:上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,p2执行完catch方法后,也会变成fulfilled,导致Promise.all()方法参数里面的两个实例都会fulfilled,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。
换一种变法:
const p1 = new Promise((resolve, reject) => {
resolve("hello");
})
.then((result) =>result)
.catch((e) =>e);
const p2 =new Promise((resolve, reject) => {
throw new Error("报错了");
}).then((result) =>result);
Promise.all([p1, p2])
.then((result) =>console.log(result))
.catch((e) =>console.log(e));
// 答:Error: 报错了
这里取消掉p2中的 catch方法,则p2就是一个rejected状态。promise.all就会接收到这个错误的状态。
Promise.race和Promise.all 比较类似,同样都是将多个Promise实例包装成一个新的Promise实例,不同的是,Promise.race会监测包含的多个实例中第一个状态发生改变,则会将第一个发生改变的promise对象的返回值传递给新的Promise回调函数。
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() =>reject(new Error('request timeout')), 5000)
})
]);
p.then(console.log).catch(console.error);
// 答:5s后输出 Error: request timeout
解析:如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数
Promise.allSettled方法也是接收一组Promise实例作为参数,包装成一个新的Promise实例。等到所有的参数实例都返回结果,不论是fulfilled还是rejected,才会执行包装实例Promise的回调函数。
const myPromise = {}
myPromise.newAllSettled=function (promises) {
return new Promise((resolve) => {
let list= [];
let len = promises.length;
let count = len;
for (let i=0; i {
list[i] = {
status: "fulfilled",
value: res,
};
},
(error) => {
list[i] = {
status: "rejected",
reason: error,
};
}
)
.finally(() => {
// 循环完成后再resolve,防止出现空属性
if (--count===0) resolve(list);
});
}
});
};
myPromise
.newAllSettled([p1, p2, p3])
.then((res) =>console.log("Promise.allSettled:", res));
执行结果:
function MyPromise(fn) {
const that=this; //对Mypromise中的this状态进行备份
this.status="pending"; //定义初始状态
this.value=""; //初始值为空
// 定义成功回调函数
function resolve(value) {
if (that.status==="pending") {
that.status="fulfilled"; //改变状态pending->fulfilled
that.value=value;
}
}
// 定义失败回调函数
function reject(value) {
if (that.status==="pending") {
that.status="rejected"; //改变状态pending->rejected
that.value=value;
}
}
// 这里的fn是执行器的意思包含resolve和reject两个回调函数
fn(resolve, reject);
}
// 在Promise的原型上定义 then 方法
MyPromise.prototype.then=function (onResolve, onReject) {
const that=this;
if (that.status==="fulfilled") {
onResolve(that.value);
}
if (that.status==="rejected") {
onReject(that.value);
}
};
// 创建一个实例
new MyPromise((resolve, reject) => {
resolve("成功了");
}).then(
(res) => {
console.log(res);
},
(err) => {
console.log(err);
}
);