从promise、process.nextTick、setTimeout出发,谈谈Event Loop中的Job queue
- Promise对象
var promise1 = new Promise(function(resolve, reject) { setTimeout(function() { resolve('foo'); }, 2000); }); promise1.then(function(value) { console.log(value); // expected output: "foo" }); console.log(promise1); // expected output: [object Promise] // 输出 // [object Promise] // "foo"
一个 Promise有以下几种状态:
pending: 初始状态,既不是成功,也不是失败状态。 fulfilled: 意味着操作成功完成。 rejected: 意味着操作失败。
-
Promise.all(iterable)
iterable:一个可迭代对象,如 Array 或 String。
var promise1 = Promise.resolve(3); var promise2 = 42; var promise3 = new Promise(function(resolve, reject) { setTimeout(resolve, 100, 'foo'); }); // Promise.all 等待所有都完成(或第一个失败) Promise.all([promise1, promise2, promise3]).then(function(values) { console.log(values); }); // expected output: Array [3, 42, "foo"] // 如果参数中包含非 promise 值,这些值将被忽略,但仍然会被放在返回数组中(如果 promise 完成的话): // this will be counted as if the iterable passed is empty, so it gets fulfilled var p = Promise.all([1,2,3]); // this will be counted as if the iterable passed contains only the resolved promise with value "444", so it gets fulfilled var p2 = Promise.all([1,2,3, Promise.resolve(444)]); // this will be counted as if the iterable passed contains only the rejected promise with value "555", so it gets rejected var p3 = Promise.all([1,2,3, Promise.reject(555)]); // using setTimeout we can execute code after the stack is empty setTimeout(function(){ console.log(p); console.log(p2); console.log(p3); }); // logs // Promise { <state>: "fulfilled", <value>: Array[3] } // Promise { <state>: "fulfilled", <value>: Array[4] } // Promise { <state>: "rejected", <reason>: 555 }
-
分析js中代码的执行顺序
单线程:执行栈->栈尾->Event Loop
// 代码执行
// 回调函数先加入任务队列,等到同步任务处理完(当前执行栈结束),才会从任务队列得到执行
setTimeout(function(){console.log(1)},0);
new Promise(function(resolve,reject){
// 决定pending->fulfilled还是pending->rejected
console.log(2);
// 传参给成功的函数
// resolve(3); // 2,7,6,4,5,1
// 加入延时 2,7,6,1,5,5
// 原因:没有同步的resolve,因此promise.then在当前的执行队列中是不存在的,只有promise从pending转移到resolve,才会有then方法
// setTimeout(()=>{
// resolve(4)
// },0)
// resolve(); // 2,7,6,NaN,5,1
// reject("error") // 2,7,6,error,1
// 没有上面resolve,reject的代码直接执行2,7,6,1,因为pending状态无法改变
}).then(function(value){console.log(value+1) // resolve时执行
}).then(function(){console.log(5)// resolve时执行
}).catch(function(error){ // 捕获reject的错误
console.log(error)
})
// process.nextTick方法指定的回调函数,总是在当前”执行栈”的尾部触发
process.nextTick(function(){console.log(6)});
// 同步
console.log(7);
// 解析:setTimeout和Promise.then任务的执行优先级是如何定义的。
- 在Job queue中的队列分为两种类型:macro-task和microTask。我们举例来看执行顺序的规定,我们设
macro-task队列包含任务: a1, a2 , a3
micro-task队列包含任务: b1, b2 , b3
执行顺序为,首先执行marco-task队列开头的任务,也就是 a1 任务,执行完毕后,在执行micro-task队列里的所有任务,也就是依次执行b1, b2 , b3,执行完后清空micro-task中的任务,接着执行marco-task中的第二个任务,依次循环。
了解完了macro-task和micro-task两种队列的执行顺序之后,我们接着来看,真实场景下这两种类型的队列里真正包含的任务(我们以node V8引擎为例),在node V8中,这两种类型的真实任务顺序如下所示:
macro-task队列真实包含任务(可以理解为宏观任务队列):
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task队列真实包含任务(可以理解为微观任务队列):
process.nextTick, Promises, Object.observe, MutationObserver
由此我们得到的执行顺序应该为:
script(主程序代码)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering
在ES6中macro-task队列又称为ScriptJobs,而micro-task又称PromiseJobs
- 实现一个简单的promise
// 函数方式
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的原型上定义链式调用的then方法
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:
}
}
// 上述就是一个初始版本的myPromise,在myPromise里发生状态改变,然后在相应的then方法里面根据不同的状态可以执行不同的操作。
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//输出1
// 但是这里myPromise无法处理异步的resolve.比如:
var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});
p.then(function(x){console.log(x)})
//无输出
// 类的方式
class PromiseM {
constructor (process) {
this.status = 'pending'
this.msg = ''
process(this.resolve.bind(this), this.reject.bind(this))
console.log(this)
return this
}
resolve (val) {
this.status = 'fulfilled'
this.msg = val
}
reject (err) {
this.status = 'rejected'
this.msg = err
}
then (fufilled, reject) {
if(this.status === 'fulfilled') {
fufilled(this.msg)
}
if(this.status === 'rejected') {
reject(this.msg)
}
}
}
var mm=new PromiseM(function(resolve,reject){
resolve('123');
});
mm.then(function(success){
console.log(success);
},function(){
console.log('fail!');
});
进一步优化方案