• 重温 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 表示紧跟在后面的表达式需要等待结果。

    异步考题总结:

    1. 所有任务都在主线程上执行,形成一个执行栈(Execution Context Stack)
    2. 在主线程之外还存在一个任务队列(Task Queen),系统把异步任务放到任务队列中,然后主线程继续执行后续的任务
    3. 一旦执行栈中所有的任务执行完毕,系统就会读取任务队列。如果这时异步任务已结束等待状态,就会从任务队列进入执行栈,恢复执行
    4. 主线程不断重复上面的第三步

    在执行任务队列中任务,包括宏任务和微任务。

    宏任务 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() 都加一级,碰到其他宏任务,就按序添加在其他宏任务后面。从一级到最后一级,从上到下按序运行。

    例子:

    1. console.time('start');
    2. setTimeout(function() {
    3. console.log(2);
    4. }, 10);
    5. setImmediate(function() {
    6. console.log(1);
    7. });
    8. new Promise(function(resolve) {
    9. console.log(3);
    10. resolve();
    11. console.log(4);
    12. }).then(function() {
    13. console.log(5);
    14. console.timeEnd('start')
    15. });
    16. console.log(6);
    17. process.nextTick(function() {
    18. console.log(7);
    19. });
    20. console.log(8);
    21. // 综合的执行顺序就是: 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方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)
    p的状态由p1p2p3决定,分成两种情况。
    (1) 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
    (2) 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    var p = Promise.race([p1, p2, p3]);

    Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
    Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。

    1. Promise.resolve(value) ;
    2. // 等价于
    3. new Promise(resolve => resolve(value));
    4. var p = Promise.reject(reason);
    5. // 等同于
    6. var p = new Promise((resolve, reject) => reject(reason));

    Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

    1. const thenable = {
    2. then(resolve) {
    3. resolve('ok');
    4. }
    5. };
    6. Promise.resolve(thenable)
    7. .then(e => {
    8. console.log(e === 'ok'); //true
    9. });
    10. Promise.reject(thenable)
    11. .catch(e => {
    12. console.log(e === thenable); // true
    13. });

    promise实现文件读取:

    1. const fs = require('fs');
    2. const path = require('path');
    3. function asyncGetFile(p){
    4. return new Promise(function(resolve, reject){
    5. fs.readFile(path.join(__dirname, p), 'utf-8', (err, data) => {
    6. if (err) reject(err);
    7. else resolve(data);
    8. })
    9. })
    10. }
    11. asyncGetFile('./some.txt')
    12. .then(data => {……})
    13. .catch(err => {……});

    用js实现sleep,用promise

    1. function sleep(time){
    2. return new Promise(resolve => setTimeout(resolve, time))
    3. }
    4. sleep(2000).then(()=>{……})
    5. // 这种方式实际上是用了 setTimeout,没有形成进程阻塞,不会造成性能和负载问题。

    实现一个 Scheduler 类,完成对Promise的并发处理,最多同时执行2个任务

    (??此点好好思考)

    1. class Scheduler {
    2. constructor() {
    3. this.tasks = [], // 待运行的任务
    4. this.usingTask = [] // 正在运行的任务
    5. }
    6. // promiseCreator 是一个异步函数,return Promise
    7. add(promiseCreator) {
    8. return new Promise((resolve, reject) => {
    9. promiseCreator.resolve = resolve
    10. if (this.usingTask.length < 2) {
    11. this.usingRun(promiseCreator)
    12. } else {
    13. this.tasks.push(promiseCreator)
    14. }
    15. })
    16. }
    17. usingRun(promiseCreator) {
    18. this.usingTask.push(promiseCreator)
    19. promiseCreator().then(() => {
    20. promiseCreator.resolve()
    21. this.usingMove(promiseCreator)
    22. if (this.tasks.length > 0) {
    23. this.usingRun(this.tasks.shift())
    24. }
    25. })
    26. }
    27. usingMove(promiseCreator) {
    28. let index = this.usingTask.findIndex(promiseCreator)
    29. this.usingTask.splice(index, 1)
    30. }
    31. }
    32. const timeout = (time) => new Promise(resolve => {
    33. setTimeout(resolve, time)
    34. })
    35. const scheduler = new Scheduler()
    36. const addTask = (time, order) => {
    37. scheduler.add(() => timeout(time)).then(() => console.log(order))
    38. }
    39. addTask(400, 4)
    40. addTask(200, 2)
    41. addTask(300, 3)

    (学习来源:牛客网)

    总结:

    1、不得不说,异步这个功能在程序的世界里,都是拔尖的复杂。还有一个也挺复杂的:多线程,这让我想起了大学学习java的时候,Thread多线程,有两个线程 A和B,其中A先调用加一,B再调用减一……

  • 相关阅读:
    【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法2】
    两地三中心部署
    刷题记录:牛客NC51180Accumulation Degree
    OSPF高级配置
    QT模态窗口与非模态窗口
    全栈工程师必须要掌握的前端Html技能
    安全狗入选《可信业务与应用安全全景视图(2022)》多个模块
    【数据结构】冒泡排序,快速排序的学习知识总结
    JdbcTemplate查询操作
    【Linux】VMware
  • 原文地址:https://blog.csdn.net/hl199626/article/details/125424927