关于javascript:关于Promise的异步执行顺序理解

44次阅读

共计 7904 个字符,预计需要花费 20 分钟才能阅读完成。

Promise

假如你曾经对 Promise 的应用相当相熟,假如你对它的实现原理相当相熟,假如你曾经写过很屡次 Promise 的实现相当相熟,那么开始往下撸。


次要是一些执行程序的总结,防止你被坑。

首先思考一个问题:promise.then() 是异步工作么?

必然是一个异步工作。它的作用就是对传入的回调函数进行解决。原理与 setTimeout 相似,setTimeout 会将回调退出到异步工作队列,待到下一轮事件循环开始时调用。而 then 对回调的解决比较复杂,因为不仅须要依赖 promise 的状态,还依赖 resolve 的调用机会,就像 setTimeout 须要依赖 timeout 一样。

总结

  1. 当所有 promise 对象的状态是 非 pending时,依照 then 的定义程序执行 then 的回调
  2. 当所有 promise 对象的状态是 pending 时,依照 resolve 函数的执行程序执行 then 的回调(即便早就定义好了 then 的回调后果也依照 resolve 的执行程序执行)
  3. 当同时存在下面的两种状况时,须要联合下面两种情景来判断
  4. resolve 能够承受不同参数,分为三类,不 then 办法的任何类型、带 then 办法的对象和 promise 对象,依据不同参数类型会有不同的解决形式

一、当所有 promise 对象的状态是 非 pending时,依照 then 的定义程序执行 then 的回调

let p1 = new Promise(resolve=> {console.log(1);
    resolve()})
let p2 = new Promise(resolve=> {console.log(2);
    resolve()})
let p3 = new Promise(resolve=> {console.log(3);
    resolve()})

p1.then(()=>{console.log(4);
})
p2.then(()=>{console.log(5);
})
p3.then(()=>{console.log(6);
})
  1. 执行程序是以后的数字 123456
  2. 这里的 p1 p2 p3 在初始化完结后全副是 fulfilled 状态,此时调用 then 办法时,Promise 会把 then 的回调 【间接】 压入到异步队列的栈底,依照谁先进入谁先执行(“先进先出”)的程序执行。假如把 p1.then 放到开端,then 变成了最初定义,此时的执行后果变成 123564

二、当所有 promise 对象的状态是 pending 时,依照 resolve 函数的执行程序执行 then 的回调(即便早就定义好了 then 的回调后果也依照 resolve 的执行程序执行)

// a
let p1 = new Promise(resolve=> {console.log(1);
    resolve()});
let p2 = new Promise(resolve=> {console.log(2);
    resolve()});
let p3 = new Promise(resolve=> {console.log(3);
    resolve()})

// b
let pp1 = p1.then(()=>{console.log(4); 
})
let pp2 = p2.then(()=>{console.log(5);
})
let pp3 = p3.then(()=>{console.log(6);
})

// c
pp1.then(()=>{console.log(7); 
})
pp2.then(()=>{console.log(8);
})
pp3.then(()=>{console.log(9);
})
// a b c 代表执行程序
  1. 执行 a 步后,返回三个 fulfilled 状态的 promise 对象,执行 b 步后依照程序退出了 then,在执行 c 步前,这里就是总结一的实现。【重点】是调用 then 办法之后,会返回一个 pending 状态的 promise 对象,因而执行 b 步后,pp1 pp2 pp3 是 pending 状态。
  2. 执行 c 步时,pp1 pp2 pp3 还是 pending 状态,因而 Promise 会把 then 的回调临时保留到 【缓存】 中(这里还没有退出到异步队列),等到 b 步中 then 的回调被执行时,在这个回调里会执行 【返回的 pending 状态的对象】(即 pp1 pp2 pp3)的 resolve 函数,调用 resolve 后【缓存】 的回调才会被退出到异步队列中
  3. 剖析下面的执行,从上往下同步执行代码,先输入 1 2 3,而后 4 5 6 的回调是第一批退出到异步队列中,7 8 9 的回调临时先 【缓存】,到这里整个同步代码执行完结;下一步执行异步工作,也就是执行 4 5 6 的回调,同时会调用【返回的 pending 状态的对象】 的 resolve 函数,而后顺次把【缓存】 的回调退出异步队列中,下面是先执行了 p1.then 的回调,因而 pp1.then 【缓存】的回调优先退出异步队列;最初 4 5 6 的回调执行完结,会持续查看异步队列中是否存在工作,发现有 7 8 9 的回调工作,因而继续执行,直到异步队列没有工作
// 把下面 b 步的 pp1 放到最初,看看后果是不是依照第三点形容的那样实现

let pp2 = p2.then(()=>{console.log(5);
})
let pp3 = p3.then(()=>{console.log(6);
})
let pp1 = p1.then(()=>{console.log(4); 
})
  1. 联合总结一,到这里的时候执行程序是 123564,在 c 步中的顺便仍旧放弃不变,pp1 是先定义,其次是 pp2 pp3。
  2. 依照总结一此时在 b 步中 then 的回调的执行程序变成 p2 p3 p1。依照下面第 3 点剖析,对应的调用 【返回的 pending 状态的对象】 的 resolve 函数程序变成 pp2 pp3 pp1,【缓存】的回调退出异步队列的程序也就变成了 pp2 pp3 pp1,从这里看在 promise 对象还是 pending 状态时,then 的回调退出程序与定义无关,与 resolve 函数 【执行】 无关,因而能够详情为谁先调用 resolve 谁先执行回调

三、当同时存在下面的两种状况时,须要联合下面两种情景来判断

let resolve1;
let resolve2;
let p1 = new Promise(resolve=> {console.log(1);
    resolve1 = resolve; 
});
let p2 = new Promise(resolve=> {console.log(2);
    resolve2 = resolve; 
});
let p3 = new Promise(resolve=> {console.log(3);
    resolve()})
let p4 = new Promise(resolve=> {console.log(4);
    resolve()})

// a
p1.then(()=>{console.log(5); 
})
// b
p2.then(()=>{console.log(6);
})
// c
p3.then(()=>{console.log(7);
})
// d
p4.then(()=>{console.log(8);
})
// e

// p1 p2 是 pending 状态,p3 p4 是 fulfilled 状态。a b c d e 是筹备给 resolve1 和 resolve2 的调用地位。
  1. 联合总结一,7 8 两个必然是先执输入 7 再输入 8,只是两头可能会蕴含 5 6。而 5 6 的理论地位就要依据 resolve1 和 resolve2 的地位来定。
  2. 状况一:resolve1 和 resolve2 地位相连
    在 a b c 执行,7 8 后面输入 5 6,无论谁先执行,最终后果都是 12345678
    在 d 执行时,7 8 两头蕴含 5 6,resolve1 resolve2 谁先执行谁先输入
    在 e 执行时,7 8 前面输入 5 6,resolve1 resolve2 谁先执行谁先输入
  3. 状况二:resolve1 和 resolve2 地位不相连,这里状况太多了,次要举例两个

    (1)resolve2 在 a 执行,resolve1 在 e 执行,后果是 12346785。首先是定义 p1.then() 时 p1 还是 pending 状态,依照总结二会把回调存到 【缓存】 中,其次是执行定义 p2.then() 之前 resolve2 曾经执行,p2 状态扭转成 fulfilled,因而 p2 依照总结一执行,而后是 p3 p4 也是依照总结一执行,最初执行 resolve1 时再把执行 p1.then 的 【缓存】 回调退出到异步队列中,最初程序是 p2 p3 p4 p1。

    (2)resolve1 在 c 执行,resolve2 在 d 执行,后果是 12345768。首先 p1 p2 依照总结二先定义了 then,会把回调存在【缓存】,而后执行 resolve1,依照总结二把 p1 的【缓存】回调退出到异步队列,接着依照总结一执行 p3.then(),再接着执行 resolve2,依照总结二把 p2 的【缓存】回调退出到异步队列,最初执行依照总结一执行 p4.then(),最终程序是 p1 p3 p2 p4。

  4. 总结就是,代码程序执行的过程中,遇到是 非 pending状态的 promise 对象,间接把回调加到异步队列,遇到 pending 状态的 promise 对象时,先有定义 then 的状况先把回调 【缓存】 起来,等到真正执行 resolve 时,再把 【缓存】 的回调加到异步队列,而且这两种状况是存在【交叉执行】

四、resolve 能够承受不同参数,分为三类,不 then 办法的任何类型、带 then 办法的对象和 promise 对象,依据不同参数类型会有不同的解决形式

1. 理解 finally
// 先来一个简略的例子
new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
}).then(()=>{console.log(9);
})

new Promise(resolve => {console.log(2);
    resolve()}).then(()=>{console.log(4);
}).then(()=>{console.log(6);
}).then(()=>{console.log(8);
}).then(()=>{console.log(10);
})
// 联合后面的总结很容易就晓得执行程序是 1 2 3 4 5 6 7 8 9 10
1.1 状况一
// 状况一:去掉输入 8 的 then,而后输入 6 的 then 替换成 finally
new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
}).then(()=>{console.log(9);
})

new Promise(resolve => {console.log(2);
    resolve()}).then(()=>{console.log(4);
}).finally(()=>{console.log(6);
}).then(()=>{console.log(10);
})
// 理论执行程序是 1 2 3 4 5 6 7 9 10
1.2 状况二
// 状况二:去掉输入 6 的 then 和输入 8 的 then,而后输入 4 的 then 替换成 finally
new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
}).then(()=>{console.log(9);
})

new Promise(resolve => {console.log(2);
    resolve()}).finally(()=>{console.log(4);
}).then(()=>{console.log(10);
})
// 执行程序按数字 1 2 3 4 5 7 9 10

比照两次批改,不难发现,第一次批改后,少了 8,第二次批改后少了 6 和 8。共同点就是两次都给都应用了 finally,不同点也是上面将会用到的 【重点】,那就是调用 finally 时两个对象的状态是不一样的,第一次批改时调用的是一个 pending 状态的对象,第二次是一个 fulfilled 状态的对象。也就是说依照不同的状态对象调用 finally,其外部的实现也存在着不一样。pending 状态时外部执行一次【异步】(这里我也不晓得外部是怎么执行的,所有把临时这个操作称为异步,上面会持续用到),fulfilled 状态
时外部实现两次【异步】。

2. resolve 参数为不带 then 办法的任何类型
// resolve 参数为不带 then 办法的任何类型(即五种原始类型和不带 then 的援用类型),这个类型 resolve 不做任何解决,会间接返回

new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
}); 

let p = 1; // null boolean number object function undefined string
new Promise(resolve => {console.log(2);
    resolve(p)
}).then(()=>{console.log(4);
}).then(()=>{console.log(6);
}).then(()=>{console.log(8);
});

let p1 = new Promise(resolve => {resolve(p)
});
  1. p1 是一个 fulfilled 状态的 promise 对象 Promise {<fulfilled>: 1},证实外部没有对这个 p 进行解决
  2. 依照后面总结,置信你就能轻松的晓得执行程序,理论执行后果 12345678
3. resolve 参数为带 then 办法的对象
// resolve 参数为带 then 办法的对象(即带 then 的对象),这个类型在 resolve 外部会做一次【异步】解决
new Promise(resolve => {console.log(1);
    resolve() // 1}).then(()=>{ // a
    console.log(3);
}).then(()=>{ // b 
    console.log(5);
}).then(()=>{console.log(7);
});

let p = {then(resolve, reject) {console.log('我是 thenable 的 then 办法');
        resolve();}
};
new Promise(resolve => {console.log(2);
    resolve(p) // 2
}).then(()=>{ // c
    console.log(6);
}).then(()=>{console.log(8);
});

let p1 = new Promise(resolve => {resolve(p)
});
// a b c 别离代表单个 then, 1 2 代表两个 resolve
  1. p1 是一个 pending 状态的 promise 对象 Promise {<pending>},证实其外部有对 p 进行解决,而且未真正调用 resolve 函数。
  2. 看控制台输入”我是 thenable 的 then 办法“在 3 之后 5 之前输入,理论执行后果是 1 2 3 我是 thenable 的 then 办法 5 6 7 8。比照能够发现,原来的输入 4 的地位变成了“我是 thenable 的 then 办法”,也就是说在应用 resolve 传递【带 then 的对象时】时外部执行了一次【异步】。
  3. 参照前三个总结,能够晓得 a 的回调必定是第一个压入到异步队列中的,其次执行 2 时把带 then 办法的对象传进去,2 外部 【并未执行真正的 resolve】 只是对这个对象进行了一次【异步】(暂且称为 d,我猜想是应用了相似 Promise.resolve() 的逻辑)解决,这个 d 的回调是第二个压入异步队列。依据总结二晓得 b c 的回调只是临时保留到【缓存】,等到 a d 的回调执行时就会把 b c 的回调【缓存】顺次加到异步队列中
4. resolve 参数为 promise 对象
4.1 当 p 的状态是 fulfilled 时
// resolve 参数为 promise 对象,这个类型 resolve 外部会做两次【异步】解决
new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
});

let p = new Promise(resolve => {resolve()
});
new Promise(resolve => {console.log(2);
    resolve(p)
}).then(()=>{console.log(8);
});

let p1 = new Promise(resolve => {resolve(p)
});
  1. p1 是一个 pending 状态的 promise 对象 Promise {<pending>},证实其外部有对 p 进行解决,而且未真正调用 resolve 函数。
  2. 理论执行后果 123578,少了输入 4 和 6,这就意味着 resolve 在解决 promise 对象作为参数时,在外部会执行两次【异步】。
  3. 参数 p 以后状态是 fulfilled,看执行后果就晓得 resolve 在解决 promise 对象为参数时的过程会在外部调用两次【异步】,状况与下面的 finally 的状况二例子有点类似,因为调用 finally 时外部同样会调用两次【异步】,至于 resolve 外部是不是真的应用了 finally 的外部解决呢?我认为不是,但至多是相似的。(因为看不到 Promise 的实现源码,而且 finally 是 ES2018 引入,Promise 是 ES2015 引入)。
4.1 当 p 的状态是 pending 时
// 假如:依照 finally 逻辑实现的解决,p 作为 pending 状态的对象传入
// 在原来的根底上进行批改,新增输入 4
new Promise(resolve => {console.log(1);
    resolve()}).then(()=>{console.log(3);
}).then(()=>{console.log(5);
}).then(()=>{console.log(7);
});

let p = new Promise(resolve => {resolve()
}).then(()=>{console.log(4);
});
new Promise(resolve => {console.log(2);
    resolve(p)
}).then(()=>{console.log(8);
});
  1. 依照后面的总结,假如的执行程序应该是 1234578,理论执行后果是 1234578,比照发现后果是统一的,跟上个例子比照多了输入 4,跟第一个例子比少了输入 6,这就意味着 resolve 在解决 promise 对象作为参数时,在外部会执行一次【异步】。
  2. 把上个例子和这个例子联合比照,同是 promise 对象,然而解决形式不一样,是不是矛盾了?其实不是,仔细观察你会发现两个例子中,p 的状态是不一样的。
  3. 参数 p 以后状态是 pending,看执行后果 resolve 在解决 promise 对象为参数时的过程会在外部只调用一次【异步】,状况与下面的 finally 的状况一例子类似。

正文完
 0