Node.js的非阻塞I/O
定义:I/O即Input/Output,一个系统的输入和输出,对外系统信息的一个交换; 可以比较为人说话和听别人说话,说话就是输出(output),听就是输入(input) 阻塞I/O和非阻塞I/O的区别就是在于系统接受输入再到输出期间,能不能接受其他输入;
例子:吃饭
- 去食堂吃饭:排队打饭,等前面的人打完饭了,轮到你打饭,等你打完饭然后开始吃饭(阻塞I/O)
- 去餐厅点菜:坐下,点菜,服务生招呼完你之后又去招呼别人,这期间你可以干自己的事,等厨房把你的菜做好然后端给你吃(非阻塞I/O)
食堂阿姨和服务生=系统,输入=点菜,输出=端菜 食堂阿姨只能一份一份的打菜,你向食堂阿姨点菜(输入),阿姨给你打菜(输出),这就是阻塞I/O,给你打菜期间不能别人点菜; 服务生点完菜之后可以服务其他人,这就是非阻塞I/O,在你的菜好端给你(输出)之前可以接受其他人的点菜(输入)
要理解非阻塞I/O要先确定系统边界,上面那个例子如果把厨师看作系统怎么都不能理解非阻塞I/O;
node.js正常情况下所有的i/o操作都是非阻塞的,它会把大量计算能力分发到其他的C++线程,等其他C++线程计算完毕之后再把计算结果回调给nodejs线程,nodejs在把这个线程的结果返回给你的应用程序
//非阻塞IO
//第二个参数传入一个回调函数,执行中可以计算1+1
var result = null;
glob(__dirname + '/**/*', function (err, res) {
result = res;
console.log(result);
});
console.log(1 + 1);
Callback回调函数
nodejs有大量的非阻塞I/O,这些非阻塞I/O程序的运行结果是需要通过回调函数来获取的,通过回调函数来编程的方式就是异步编程
callback函数要遵循一个规则,第一个参数是error,后面的参数才是结果
function interview(callback) {
setTimeout(() => {
if (Math.random() < 0.8) {
callback('success');
} else {
throw new Error('fail');
}
}, 500);
}
try {
interview(function (val) {
console.log(val);
});
} catch (e) {
console.log('error', e);
}
上面这段代码回调函数throw error
并不会被try catch
捕获到,而是直接抛给nodejs全局
为什么会这样子呢,因为try catch
和调用栈是很有关系的,代码会存在函数调用函数的情况的,每个函数调用其他函数的时候会在调用栈里加一层,一层层累加形成一个调用的链条体系,在程序里就形成了一个栈结构
try catch的机制就是,比如在调用栈的第五项加入一个try catch,栈顶元素抛出一个错误,这个错误会逐层往上抛,抛到第五项的时候会被try catch捕获到;
而上面那段代码的settimeout里面的函数是一个另外事件循环里面回调的,新的事件循环都是一个全新的调用栈,所throw new Error
就会被抛到nodejs全局,所以在settimeout异步任务里throw new Error是不会被外面的try catch捕获到,所以错误也需要callback传送出去;
function interview(callback) {
setTimeout(() => {
if (Math.random() < 0.8) {
callback('success');
} else {
callback(new Error('fail'));
}
}, 500);
}
interview(function (res) {
if (res instanceof Error) {
return console.log('car');
}
console.log(res);
});
但是nodejs回调函数有很多参数,所以约定第一个参数为error。第二个第三个为回调的结果
事件循环(event loop)
事件循环例子 比如在一个餐厅,一个客人点了一个鱼香茄子,服务生告诉厨房做一个鱼香茄子,建立一个鱼香茄子的线程,这时候另外一个客人点了一个小炒肉,服务生告诉厨房做一个小炒肉,又来一个客人点了一个番茄炒蛋,服务生告诉厨房做一个番茄炒蛋;假设说鱼香茄子做好了,服务生就把鱼香茄子端到你面前,结束了这个线程,这时候番茄炒蛋也好了,服务生也把这个番茄炒蛋端出去,最后小炒肉做好了,餐厅的任务就完成了; 这就是事件循环的例子,是一个处理事件的循环,每当有事件不断进来的时候,一旦其中有事件做完了就会把事件所需要的数据回调出去;
const eventLoop = {
queue: [], //空队列
loop() {
while (this.queue.length) {
var callback = this.queue.shift();
callback();//调用栈的底部,这个回调之前的的代码全部都是c++
}
setTimeout(this.loop.bind(this), 50);
},
add(callback) {
this.queue.push(callback);
},
};
eventLoop.loop();
setTimeout(() => {
eventLoop.add(function () {
console.log(1);
});
}, 500);
setTimeout(() => {
eventLoop.add(function () {
console.log(2);
});
}, 800);
每个事件循环都是一个全新的调用栈,调用栈的地步就是一个event loop的触发事件,可以理解为第一个callback()
Promise
当前的事件循环得不到的结果,在未来的事件循环会得到结果,它是一个状态机
- pending
- fulfilled/resolved(正确)
- rejected(错误)
var promise = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(2);
reject(new Error());
}, 200);
})
.then((res) => {
// console.log(res);
})
.catch((error) => {
console.log(error);
});
console.log(promise);
.then 和 .catch
- resolved状态的Promise会回调后面的第一个.then
- rejected状态的Promise会回调后面的第一个.catch
- 任何一个rejected状态后面没有.catch的Promise,都会造成浏览器/node环境的全局错误
Promise可以解决很多异步流程控制问题,上面callback的代码改写为promise
var promise = interview();
function interview() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.8) {
resolve('success');
} else {
reject(new Error());
}
}, 500);
});
}
promise
.then((res) => {
console.log(res);
})
.catch((error) => {
console.log(error);
});
执行then和catch会返回一个新的Promise,该Promise最终状态根据then和catch的回调函数的执行结果决定
- 如果回调函数最终是throw,该promise是rejected状态
var promise = interview();
var promise2 = promise.then((res) => {
throw new Error();
});
setTimeout(() => {
console.log(promise);
console.log(promise2);
}, 800);
function interview() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.8) {
resolve('success');
} else {
reject(new Error());
}
}, 500);
});
}
- 如果回调函数最终是return,该promise是resolved状态,即便在catch中return也是resolved
- 如果回调函数最终return了一个Promise,该Promise会和回调函数return的Promise保持一致,这样就可以在Promise的链式调用中串行的执行任务
var promise = interview();
var promise2 = promise.then((res) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我接受');
}, 400);
});
});
//在800ms和1000ms的时候打印
setTimeout(() => {
console.log(promise);
console.log(promise2);
}, 800);
setTimeout(() => {
console.log(promise);
console.log(promise2);
}, 1000);
function interview() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 1) {
resolve('success');
} else {
reject(new Error());
}
}, 500);
});
}
800ms时promise2的状态还在pending,打印的promise2的状态和reutn new promise的状态一致;
所以链式调用就可以改写为
var promise = interview(1)
.then((res) => {
//then里面return一个promise,后续的操作会等promise完成之后再继续
return interview(2);
})
.then((res) => {
return interview(3);
})
.then((res) => {
console.log('我笑了');
})
.catch((error) => {
console.log('我哭了' + error.round);
});
function interview(round) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.3) {
resolve('success');
} else {
var error = new Error('fail');
error.round = round;
reject(error);
}
}, 500);
});
}
并发异步Promise.all([])
Promise.all([])接收一个数组,当数组里的promise全部是resolved状态时调用.then,只要有一个rejected就调用.catch
Promise.all([interview('geekbang'), interview('tencent')])
.then((res) => {
console.log('我笑了');
})
.catch((error) => {
console.log('我在' + error.name + '挂了');
});
function interview(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.3) {
resolve('success');
} else {
var error = new Error('fail');
error.name = name;
reject(error);
}
}, 500);
});
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!