Promise
假如你曾经对Promise的应用相当相熟,假如你对它的实现原理相当相熟,假如你曾经写过很屡次Promise的实现相当相熟,那么开始往下撸。
次要是一些执行程序的总结,防止你被坑。
首先思考一个问题:promise.then() 是异步工作么?
必然是一个异步工作。它的作用就是对传入的回调函数进行解决。原理与 setTimeout 相似,setTimeout 会将回调退出到异步工作队列,待到下一轮事件循环开始时调用。而 then 对回调的解决比较复杂,因为不仅须要依赖promise的状态,还依赖 resolve 的调用机会,就像 setTimeout须要依赖 timeout 一样。
总结
- 当所有 promise 对象的状态是
非pending
时,依照 then 的定义程序执行 then 的回调 - 当所有 promise 对象的状态是
pending
时,依照 resolve 函数的执行程序执行 then 的回调(即便早就定义好了 then 的回调后果也依照 resolve 的执行程序执行) - 当同时存在下面的两种状况时,须要联合下面两种情景来判断
- 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);})
- 执行程序是以后的数字 123456
- 这里的p1 p2 p3在初始化完结后全副是 fulfilled 状态,此时调用 then 办法时, Promise 会把 then 的回调【间接】压入到异步队列的栈底,依照谁先进入谁先执行(“先进先出”)的程序执行。假如把 p1.then 放到开端,then 变成了最初定义,此时的执行后果变成 123564
二、当所有 promise 对象的状态是pending
时,依照 resolve 函数的执行程序执行 then 的回调(即便早就定义好了 then 的回调后果也依照 resolve 的执行程序执行)
// alet 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()})// blet pp1 = p1.then(()=>{ console.log(4); })let pp2 = p2.then(()=>{ console.log(5);})let pp3 = p3.then(()=>{ console.log(6);})// cpp1.then(()=>{ console.log(7); })pp2.then(()=>{ console.log(8);})pp3.then(()=>{ console.log(9);})// a b c 代表执行程序
- 执行 a 步后,返回三个 fulfilled 状态的 promise 对象,执行 b 步后依照程序退出了then,在执行 c 步前,这里就是总结一的实现。【重点】是调用 then 办法之后,会返回一个 pending 状态的 promise 对象,因而执行 b 步后,pp1 pp2 pp3 是 pending 状态。
- 执行 c 步时,pp1 pp2 pp3 还是 pending 状态,因而 Promise 会把 then 的回调临时保留到【缓存】中(这里还没有退出到异步队列),等到 b 步中 then 的回调被执行时,在这个回调里会执行【返回的 pending 状态的对象】(即 pp1 pp2 pp3) 的 resolve 函数,调用 resolve 后【缓存】的回调才会被退出到异步队列中
- 剖析下面的执行,从上往下同步执行代码,先输入 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); })
- 联合总结一,到这里的时候执行程序是 123564,在 c 步中的顺便仍旧放弃不变, pp1是先定义,其次是 pp2 pp3。
- 依照总结一此时在 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()})// ap1.then(()=>{ console.log(5); })// bp2.then(()=>{ console.log(6);})// cp3.then(()=>{ console.log(7);})// dp4.then(()=>{ console.log(8);})// e// p1 p2 是pending状态, p3 p4 是 fulfilled 状态。a b c d e 是筹备给 resolve1 和 resolve2 的调用地位。
- 联合总结一,7 8 两个必然是先执输入 7 再输入 8,只是两头可能会蕴含 5 6。而 5 6 的理论地位就要依据 resolve1 和 resolve2 的地位来定。
- 状况一:resolve1 和 resolve2 地位相连
在 a b c 执行,7 8 后面输入 5 6,无论谁先执行,最终后果都是 12345678
在 d 执行时,7 8 两头蕴含 5 6,resolve1 resolve2 谁先执行谁先输入
在 e 执行时,7 8 前面输入 5 6,resolve1 resolve2 谁先执行谁先输入状况二: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。
- 总结就是,代码程序执行的过程中,遇到是非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替换成finallynew 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替换成finallynew 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 stringnew 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)});
- p1 是一个 fulfilled 状态的 promise 对象 Promise {<fulfilled>: 1} ,证实外部没有对这个 p 进行解决
- 依照后面总结,置信你就能轻松的晓得执行程序,理论执行后果 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
- p1 是一个pending状态的 promise 对象 Promise {<pending>} ,证实其外部有对 p 进行解决,而且未真正调用 resolve 函数。
- 看控制台输入”我是thenable的then办法“在 3 之后 5 之前输入,理论执行后果是 1 2 3 我是thenable的then办法 5 6 7 8。比照能够发现,原来的输入 4 的地位变成了 “我是thenable的then办法”,也就是说在应用 resolve 传递 【带then的对象时】时外部执行了一次【异步】。
- 参照前三个总结,能够晓得 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)});
- p1 是一个pending状态的 promise 对象 Promise {<pending>} ,证实其外部有对 p 进行解决,而且未真正调用 resolve 函数。
- 理论执行后果 123578,少了输入 4 和 6,这就意味着 resolve 在解决 promise 对象作为参数时,在外部会执行两次【异步】。
- 参数 p 以后状态是 fulfilled,看执行后果就晓得 resolve 在解决 promise 对象为参数时的过程会在外部调用两次【异步】,状况与下面的 finally 的状况二例子有点类似,因为调用 finally 时外部同样会调用两次【异步】,至于 resolve 外部是不是真的应用了 finally的外部解决呢?我认为不是,但至多是相似的。(因为看不到Promise的实现源码,而且 finally 是 ES2018 引入,Promise 是 ES2015 引入)。
4.1 当 p 的状态是 pending 时
// 假如:依照 finally 逻辑实现的解决,p 作为 pending 状态的对象传入// 在原来的根底上进行批改,新增输入 4new 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);});
- 依照后面的总结,假如的执行程序应该是 1234578,理论执行后果是 1234578,比照发现后果是统一的,跟上个例子比照多了输入4,跟第一个例子比少了输入6,这就意味着 resolve 在解决 promise 对象作为参数时,在外部会执行一次【异步】。
- 把上个例子和这个例子联合比照,同是 promise 对象,然而解决形式不一样,是不是矛盾了?其实不是,仔细观察你会发现两个例子中,p 的状态是不一样的。
- 参数 p 以后状态是 pending,看执行后果 resolve 在解决 promise 对象为参数时的过程会在外部只调用一次【异步】,状况与下面的 finally 的状况一例子类似。