什麼是 promise?html
什麼是 async 和 await前端
·同步代碼·
,一句話總結,async 函數就是 Generator 函數的語法糖,返回了一個 promise.resolve() 的結果。阮一峯老師的 async 教程上面提到了一個異步的問題,咱們前端er都知道 JavaScript - 是單線程的,若是存在多個任務的時候,就會有任務隊列進行排隊,而後一一執行任務。git
不着急介紹 promise 的詳情,首先咱們從最開始的同步和異步講起:github
簡單的理解ajax
console.log('synchronous'); //咱們能當即獲得 synchronous
複製代碼
簡單的理解chrome
來看一個圖數組
一個瀏覽器一般由如下幾個常駐的線程:promise
渲染引擎
和js引擎線程
是不能同時進行的,渲染線程
在執行任務的時候,js引擎線程
會被掛起。由於如果在渲染頁面的時候,js處理了DOM,瀏覽器就不知道該聽誰的了渲染引擎
:Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trdent引擎,FireFox用的是Gecko引擎。不一樣的引擎對同一個樣式的實現不一致,就致使瀏覽器的兼容性問題。JS引擎:
js引擎能夠說是js虛擬機,負責解析js代碼的解析和執行。一般有如下步驟:
JS引擎也是不一樣的
:Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。總結一點:JavaScript是單線程的,可是瀏覽器不是單線程的。一些I/O操做,定時器的計時和事件監聽是由其餘線程完成的。瀏覽器
開局一張圖bash
導圖要表達的內容用文字來表述的話: 1.同步和異步任務分別進入不一樣的執行"場所" 2.同步的進入主線程,異步的進入Event Table並註冊回調函數到 Event Queue 中。 3.當主線程執行完畢之後,而後會去 Event Queue 查詢,時候若是存在的函數,放進主線程中繼續執行。 4.上述就是event loop的執行
說了這麼多文字,不如直接一段代碼更直白:
console.log('script start');
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function cb() {
console.log('promise2');
});
console.log('script end');
// script start
// promise1
// script end
// promise2
複製代碼
分析這段代碼:
首先執行,打印 script start
而後進入 promise 函數打印 promise1,執行 resolve()
在 then 執行的時候咱們把異步回調放進了 event table 中註冊相關的回調函數。
new promise 執行完畢,回調函數cb() 進入Event Queue。
執行 打印 script end;
主線程從Event Queue讀取回調函數 cb 並執行。
複製代碼
微任務
的時候,優先執行 微任務
macro-task(宏任務):包括總體代碼script,setTimeout,setInterval micro-task(微任務):Promise,process.nextTick
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
/** * script start * async1 start * async2 * promise1 * script end * async1 end * promsise2 * setTimerout */
複製代碼
注意幾個點
一、js是單線程的。
二、promise被定義後是當即執行的,可是他的resolve是異步的。
三、promise的異步優先級高於setTimeout。
四、async會返回一個promise對象,await關鍵字會讓出線程。
複製代碼
- 定義異步函數 async1, 異步函數 async2
1. console.log('script start'); 執行 (1)`script start`
2. setTimeout 執行,異步放入異步隊列中,注意這是一個宏任務(咱們標記爲 macro1)
3. 執行 async1(), 打印 (2)`async1 start`, 執行 async1() 中的 await async2(): 打印 (3)`async2`;
遇到 await 後面的函數進入任務隊列,這裏又註冊一個微任務(咱們標記爲 mico1);到這裏 async1() 就執行完了
4. 執行 new Promise:打印 (4)`promise1`,執行 resolve();
而後在 then 中註冊回調函數,console.log('promise2') 函數進入任務隊列;
註冊 event queue(咱們標記爲 mico2).這裏 new Promise 就執行完了。
5. 執行 console.log('script end');, 打印 (5) `script end`;
6. 上面👆五步把主線程都執行完畢了,而後去event queue 查找有沒有註冊的函數;
咱們發現了(macro 1, mico1, mico2),按照優先執行微任務的原則,咱們按照這樣的順序執行 mico1 > mico2 > macro1。
打印:(6) `async1 end` (7) `promise2` (8) `setTimeout`
複製代碼
[!warning]可能你會在不一樣瀏覽器發現不一樣結果,這是由於不一樣瀏覽器和版本的不一樣遵循的 promise 規則不一樣。這裏是按照較新版本的 chrome(68+) 執行的結果,具體參考(www.w3.org/2001/tag/do…
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
})
promise.then(() => {
console.log(3);
})
console.log(4);
複製代碼
分析
首先Promise新建後當即執行,因此會先輸出1,2,而Promise.then()內部的代碼在當次事件循環的結尾當即執行,因此會先輸出4,最後輸出3.
QA:1 2 4 3
const promise = new Promise((resolve, reject) => {
resolve('success1');
reject('error');
resolve('success2');
});
promise.then((res) => {
console.log('then:', res);
}).catch((err) => {
console.log('catch:', err);
})
複製代碼
分析
resolve函數Promise對象的狀態從「未完成」變爲「成功」(即從pending變爲resolved),在異步操做成功時調用,並將異步操做的結果,做爲參數傳遞出去; reject函數將Promise對象的狀態從「未完成」變爲「失敗」(即從pending變爲rejected),在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。 而一旦狀態改變,就不會有再變。
因此代碼中的reject('error')
;不會有做用。 Promise
只能resolve
一次,剩下的調用都會被忽略。 因此第二次resolve('success')
;也不會有做用。
QA:then:success1
(1) promise 對象初始化狀態爲 pending
(2) 當調用resolve(成功),會由pending => fulfilled
(3) 當調用reject(失敗),會由pending => rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
this.state = PENDING;
this.value = null;
this.reason = null;
const resolve = value => {
if (this.state === PENDING) {
this.state = FULFILLED;
this.value = value;
}
};
const reject = reason => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason = reason;
}
};
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
複製代碼
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function MyPromise(executor) {
this.state = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.state === PENDING) {
this.state === FULFILLED;
this.value === value;
this.onFulfilledCallbacks.forEach(fuc =>{
fuc();
});
}
};
const reject = reason => {
if (this.state === PENDING) {
this.state = REJECTED;
this.reason === reason;
this.onRejectedCallbacks.forEach(fuc =>{
fuc();
})
}
};
try {
executor(resolve, reject);
} catch (reason) {
reject(reason);
}
}
/* - then方法接受兩個參數onFulfilled、onRejected,它們分別在狀態由PENDING改變爲FULFILLED、REJECTED後調用 - 一個promise可綁定多個then方法 - then方法能夠同步調用也能夠異步調用 - 同步調用:狀態已經改變,直接調用onFulfilled方法 - 異步調用:狀態仍是PENDING,將onFulfilled、onRejected分別加入兩個函數- - 數組onFulfilledCallbacks、onRejectedCallbacks, - 當異步調用resolve和reject時,將兩個數組中綁定的事件循環執行。 */
MyPromise.prototype.then = function(onFulfilled,onRejected){
switch(this.state){
case FULFILLED:
onFulfilled(this.value);
break;
case REJECTED:
onRejected(this.reason);
break;
case PENDING:
this.onFulfilledCallbacks.push(()=>{
onFulfilled(this.value);
});
this.onRejectedCallbacks.push(() => {
onRejected(this.reason);
})
break;
}
}
// 因爲catch方法是then(null, onRejected)的語法糖,因此這裏也很好實現
MyPromise.prototype.catch = function(onRejected){
return this.then(null, onRejected);
}
複製代碼
Promise.all = function(promises) {
return new Promise(function(resolve, reject) {
var resolvedCounter = 0
var promiseNum = promises.length
var resolvedValues = new Array(promiseNum)
for (var i = 0; i < promiseNum; i++) {
(function(i) {
Promise.resolve(promises[i]).then(function(value) {
resolvedCounter++
resolvedValues[i] = value
if (resolvedCounter == promiseNum) {
return resolve(resolvedValues)
}
}, function(reason) {
return reject(reason)
})
})(i)
}
})
複製代碼
Promise.all([a,b,c].map(p => p.catch(e => {...})))
.then(res => {...})
.catch(err => {...});
複製代碼
function loadImageAsync(url) {
return new Promise(function(resolve,reject) {
var image = new Image();
image.onload = function() {
resolve(image)
};
image.onerror = function() {
reject(new Error('Could not load image at' + url));
};
image.src = url;
});
}
複製代碼
衍生:這裏須要先併發請求3張圖片,當一張圖片加載完成後,又會繼續發起一張圖片的請求,讓併發數保持在3個,直到須要加載的圖片都所有發起請求。咱們應該怎麼作?