正经文章的地方

Promise

一、promise的概念

对于延时的、异步的操作,在期待他们进行链式的一环一环进行的情况下,之前使用的例如回调的方法,都是通过代码的互相调用来实现的,需要自己上下翻找代码,对于成为多个函数的回调方法的公共方法,则需要自己去排查每个引用和位置,不安全也不利于代码管理。

promise提供了一个链状的包装,将这些需要异步进行的代码勾连起来,通过提供promise、resolve、reject等方法,不再需要手动去管理这些复杂的互相调用关系,也让代码看起来(即时在异步的情况下)成为一条通顺的逻辑链。

二、控制反转与反控制反转

异步调用的旧处理方法:回调函数,在功能上也能实现需要的内容,但是回调在阅读代码时会造成理解困难、代码糅杂,最重要的是不安全,也就是所谓的“控制反转”,而promise则通过反控制反转来保证代码的可读性和安全性。
控制反转,是指本来我们指望block A在完成一个可能需要一定时间的操作后再进行后续操作,但是通过回调,直接把block A交付给了另一个block B作为回调,A失去了对自己的控制,哪里、什么时候、调用几次,都将由持有它的block B去决定。所以就算A本身没错,也可能因为B的出错导致问题。
反控制反转,是指block A不再将自己交付出去,而是在完成后发送一个完成事件,去通知其他block做后续操作,将控制返还给了调用代码。
好处:通过promise这样的操作,block A的调用安全了,可以自己掌控,代码也比较容易阅读理解,而且一个A可以有多个其他block去监听,A自己不用关心下级监听的是谁、有多少、要做什么。

三、MicroTask和MacroTask

promise控制的异步属于MicroTask,而setTimeout控制的异步属于MacroTask。

页面渲染会在任务(Task)执行完毕之后,而MicroTask会在任务队列(Task Queue)之后,页面渲染之前执行,MacroTask则是在页面渲染之后执行,整体流程如下:

Task Queue → Micro Task → 页面渲染 → Macro Task

macro-task: script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering
micro-task: process, nextTick, Promise, Object.observe, MutationObserve

所以注意,同时出现async await promise settimeout的时候,settimeout一定是最后执行的。

习题:
参考1:promise、async和await之执行顺序的那点事
参考2:一道题引发的EventLoop思考

async function async1() {
    // 4. 顺序运行到console log,普通地按顺序第三个执行输出
    console.log('async1 start');
    // 5. 顺序运行到await,发现应该调用async2,就去找async2执行
    await async2();
    // 12. 最后一个microTask执行完之后,再进行最后一步的操作,输出async1 end
    console.log('async1 end');
};

async function async2() {
    // 6. async2执行完毕,输出async2,同时由于await让出线程,所以暂时把返回的回调加入microTask
    console.log('async2');
}

// 1. 进入Task Queue,作为Task第一个进入任务stack进行运行
console.log('script start');

// 2. setTimeout定义了,但是不执行,先丢入macroTask等待执行
setTimeout(function () {
    // 13. 最后的最后,执行macroTask,把setTimeout输出
    console.log('setTimeout');
}, 0);

// 3. 同步任务,进入Task Queue,按顺序,第二个运行
async1();

// 7. 执行到这里,定义了一个Promise,Promise一旦被定义,立刻执行,于是进入方法执行
new Promise(function (resolve) {
    // 8. 正常按顺序执行,打印promise1
    console.log('promise1');
    // 9. 把resolve放入micro-task队列
    resolve();
}).then(function() {
    // 11. 重点!!!先执行promise2,因为async2是用async定义的,所以返回一个promise,
    // 也就是await async2其实是等同于async2().then(() => {console.log}),
    // 所以在执行到await这个任务的时候,其实还丢了一个resolve进入microTask,实际上效果是:
    // await → promise resolve → async2 resolve,
    // 因此promise2先输出(此处和部分浏览器特殊情况有关,暂时按新的chrome规范来解释)
    console.log('promise2');
});

// 10. 正常顺序执行Task到末尾,打印scrip end,之后开始执行microTask
console.log('script end');

// ————————————————————————RESULT————————————————————————
// script start
// async1 start
// async2
// promise1
// script end
// promise2
// async2 end
// setTimeout

——剩下的等看完再补充——