重温 JavaScript 系列(4):实现异步的方法、 promise实现文件读取、Promise的并发处理?
实现异步的方法
回调函数(Callback)、事件监听、发布订阅、Promise/A+、生成器Generators/ yield、async/await
async/await函数对 Generator 函数的改进,体现在以下三点:
- 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。
- 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
异步考题总结:
- 所有任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
- 在主线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
- 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
- 主线程不断重复上面的第三步
在执行任务队列中任务,包括宏任务和微任务。
宏任务 Macrotask
宏任务是指Event Loop在每个阶段执行的任务
微任务 Microtask
微任务是指Event Loop在每个阶段之间执行的任务
在node V8中,这两种类型的真实任务顺序如下所示:
宏任务 Macrotask队列真实包含任务:
script(主程序代码),setTimeout, setInterval, setImmediate, I/O, UI rendering
注意:setTimeout 如果设置了大于0的延时,就会在 setImmediate后面执行。
微任务 Microtask队列真实包含任务:
process.nextTick, Promises, Object.observe, MutationObserver
PS:先执行主线程中的同步任务。再按照宏任务和微任务的顺序执行,一个await()、then()、catch() 都加一级,碰到其他宏任务,就按序添加在其他宏任务后面。从一级到最后一级,从上到下按序运行。
例子:
console.time('start');
setTimeout(function() {
console.log(2);
}, 10);
setImmediate(function() {
console.log(1);
});
new Promise(function(resolve) {
console.log(3);
resolve();
console.log(4);
}).then(function() {
console.log(5);
console.timeEnd('start')
});
console.log(6);
process.nextTick(function() {
console.log(7);
});
console.log(8);
// 综合的执行顺序就是: 3——>4——>6——>8——>7——>5——>start: 7.009ms——>1——>2。
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
- Node 端,microtask 在事件循环的各个阶段之间执行
- 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行
(参考 帖子:https://blog.csdn.net/Fundebug/article/details/86487117)
var p = Promise.all([p1, p2, p3]);
Promise.all
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise
实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise
实例,再进一步处理。(Promise.all
方法的参数可以不是数组,但必须具有 Iterator
接口,且返回的每个成员都是 Promise
实例。)
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1) 只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2) 只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
var p = Promise.race([p1, p2, p3]);
Promise.race
方法同样是将多个Promise
实例,包装成一个新的Promise
实例。只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise
实例的返回值,就传递给p
的回调函数。
Promise.race
方法的参数与Promise.all
方法一样,如果不是 Promise
实例,就会先调用下面讲到的Promise.resolve
方法,将参数转为Promise
实例,再进一步处理。
Promise.resolve(value) ;
// 等价于
new Promise(resolve => resolve(value));
var p = Promise.reject(reason);
// 等同于
var p = new Promise((resolve, reject) => reject(reason));
Promise.reject()
方法的参数,会原封不动地作为reject
的理由,变成后续方法的参数。这一点与Promise.resolve
方法不一致。
const thenable = {
then(resolve) {
resolve('ok');
}
};
Promise.resolve(thenable)
.then(e => {
console.log(e === 'ok'); //true
});
Promise.reject(thenable)
.catch(e => {
console.log(e === thenable); // true
});
promise实现文件读取:
const fs = require('fs');
const path = require('path');
function asyncGetFile(p){
return new Promise(function(resolve, reject){
fs.readFile(path.join(__dirname, p), 'utf-8', (err, data) => {
if (err) reject(err);
else resolve(data);
})
})
}
asyncGetFile('./some.txt')
.then(data => {……})
.catch(err => {……});
用js实现sleep,用promise
function sleep(time){
return new Promise(resolve => setTimeout(resolve, time))
}
sleep(2000).then(()=>{……})
// 这种方式实际上是用了 setTimeout,没有形成进程阻塞,不会造成性能和负载问题。
实现一个 Scheduler 类,完成对Promise的并发处理,最多同时执行2个任务
(??此点好好思考)
class Scheduler {
constructor() {
this.tasks = [], // 待运行的任务
this.usingTask = [] // 正在运行的任务
}
// promiseCreator 是一个异步函数,return Promise
add(promiseCreator) {
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve
if (this.usingTask.length < 2) {
this.usingRun(promiseCreator)
} else {
this.tasks.push(promiseCreator)
}
})
}
usingRun(promiseCreator) {
this.usingTask.push(promiseCreator)
promiseCreator().then(() => {
promiseCreator.resolve()
this.usingMove(promiseCreator)
if (this.tasks.length > 0) {
this.usingRun(this.tasks.shift())
}
})
}
usingMove(promiseCreator) {
let index = this.usingTask.findIndex(promiseCreator)
this.usingTask.splice(index, 1)
}
}
const timeout = (time) => new Promise(resolve => {
setTimeout(resolve, time)
})
const scheduler = new Scheduler()
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then(() => console.log(order))
}
addTask(400, 4)
addTask(200, 2)
addTask(300, 3)
总结:
1、不得不说,异步这个功能在程序的世界里,都是拔尖的复杂。还有一个也挺复杂的:多线程,这让我想起了大学学习java的时候,Thread多线程,有两个线程 A和B,其中A先调用加一,B再调用减一……
52软件资源库 » 重温 JavaScript 系列(4):实现异步的方法、 promise实现文件读取、Promise的并发处理?