導言:在學習 async/awit以前, 咱們頗有必要學習一下 , 迭代器(lterator)和生成器(Generator)html
導言:用循環語句迭代數據時,必需要初始化有關變量來記錄每一次迭代在數據集合中的位置,
迭代器的使用能夠極大地簡化數據操做,因而es6也向js中添加了這個迭代器特性。新的數組方法和新的集合類型(例如:Set與Map集合)都依賴迭代器的實現,甚至異步編程中均可以使用迭代器。
但這此以前,咱們必定要了解一下迭代器的背後的歷史。前端
var colors = ['red','green','blue']; for(var i = 0 ,len = colors.length ; i < len ; i ++) { console.log(colors[i]) }
上面是一段標準的for循環代碼,經過變量i來跟蹤colors數組的索引。
雖然循環語句的語法簡單,可是若是將多個循環嵌套則須要追蹤多個變量,代碼的複雜度會大大增長。迭代器的出現爲了消除這種複雜性並減小循環中的錯誤。java
迭代器是一種特殊對象,它具備一些專門爲迭代過程設計的專有接口,全部的迭代器對象都有一個next()方法,每次調用都返回一個結果對象。結果對象有兩個屬性:一個是value,表示下一個將要返回的值;另外一個是done, 它是一個布爾類型的值,當沒有更多可返回數據時返回數據時返回true。迭代器還會保存一個內部指針,用來指向當前集合中值的位置,每調用一次next()方法,都會返回下一個可用的值。
咱們用es5的語法來本身寫一個迭代器
按個人理解,這應該屬於閉包的用處node
function createIterator(items) { var i = 0; return { next :function() { var done = ( i >= items.length); var value = !done ? items[i++] :undefined; return { done : done, value : value } } } } var iterator = createIterator([1,2,3]); console.log(iterator.next()) //{ done: false, value: 1 } console.log(iterator.next()) //{ done: false, value: 2 } console.log(iterator.next()) //{ done: false, value: 3 } console.log(iterator.next()) //{ done: true, value: undefined } console.log(iterator.next()) //{ done: true, value: undefined }
迭代器的編寫規則也一樣複雜,但es6同時還引入一個生成器對象,它可以讓建立迭代器對象過程變得簡單。
note: 檢測對象是否爲可迭代對象es6
function isIterable(object) { return typeof object[Symbol.iterator] === "function"; }
生成器是一種返回迭代器的函數,經過 function 關鍵字後的 星號(*)來表示, 函數中會用到新的關鍵字 yield 。就像這樣 :面試
//生成器 function *createIterator() { yield 1; yield 2; yield 3; } // 生成器的調用方式與普通函數相同, 只不過返回的是一個迭代器 let iterator = createIterator(); console.log(iterator.next())//{ value: 1, done: false } console.log(iterator.next())//{ value: 2, done: false } console.log(iterator.next())//{ value: 3, done: false } console.log(iterator.next())//{ value: undefined, done: true }
在這個實例中, createIterator()前的星號代表它是一個生成器;yield 關鍵字也是 es6的新特性 , 可用經過它來指定調用迭代器的 next()方法時的返回值及返回順序。生成迭代器後, 連續3次調用它的next()方法返回3個不一樣的值 , 分別是 1,2,3。 生成器的調用過程與其餘函數同樣 , 最終返回的是建立好的迭代器。
生成器函數最重要的是,每當執行完一條 yield 語句後函數就會自動中止執行.ajax
生成器使人興奮的特性多與異步編程有關, javaScript中的異步編程有利有弊:簡單任務的異步化很是容易實現;而複雜任務的異步化會帶來不少管理代碼的挑戰。因爲生成器支持在函數中暫停代碼執行,於是能夠深刻挖掘異步處理的更多用法。若是須要嵌套回調化序列化一系列的異步操做,事情會變得很是複雜。此時,生成器和yield語句就派上用場了。編程
因爲執行yield 語句會暫停當前函數的執行過程並等待下一次調用 next()方法。所以你能夠建立一個函數,在函數中調用生成器生成相應的迭代器,從而在不用回調函數的基礎上實現異步調用next()方法,就像這樣:數組
function run(taskDef) { // 建立一個無使用限制的迭代器 let task = taskDef(); // 開始執行任務 let result = task.next(); //循環調用next()的函數 function step() { // 若是任務未完成,則繼續執行 if(!result.done) { result = task.next(); step(); } } //開始迭代執行 step(); } // 執行 run(function *() { console.log(1); yield; console.log(2); yield; console.log(3) })
函數run()接收一個生成器函數做爲參數,這個函數定義了後續要執行的任務,生成一個迭代器並將它存儲其在變量task中。首次調用迭代器的next()方法時,返回的結果被存儲起來稍後繼續使用。step()函數會檢查result.done的值,若是爲false則執行迭代器的next()方法,並再次執行step()操做。每次調用next()方法,返回的最新信息老是覆蓋變量result。在代碼的最後,初始化執行step()函數並開始整個的迭代過程,每次經過檢查result.done來肯定是否有更多任務須要執行。promise
給任務執行器傳遞數據的最簡單方法是,將值經過迭代器的next()方法傳入做爲yield的生成值供下次調用。這下面這段代碼中,只須要將result.value傳入next()方法便可:
function run(taskDef) { // 建立一個無使用限制的迭代器 let task = taskDef(); //開始執行任務 let result = task.next(); // 循環調用next()的函數 function step() { //若是任務未完成 , 則繼續執行 if(!result.done) { result = task.next(result.value); step(); } } //開始迭代執行 step(); } run(function *() { let value = yield 1; console.log(value); // 1 value = yield value + 3; console.log(value); })
這例子會向控制檯輸出兩個數值 1 和 4.
如今數據已經可以在yield調用間互相傳遞了,只須要一個小小的改變便能支持異步調用。
以前的例子只是在多個yield調用間來回傳遞靜態數據,而等待一個異步過程有些不一樣。任務執行器須要知曉回調函數是什麼以及如何使用它。因爲 yield表達式會將值返回給任務執行器,全部的函數調用都會返回一個值,所以在某種程度上這也是一個異步操做,任務執行器會一直等待直到操做完成。
下面咱們定義一個異步操做: function fetchData() { return function(callback) { setTimeout(function() { callback(null,'Hi'); }, 50) } }
理解了函數中異步過程的運做方式,咱們能夠將任務執行器稍做修改。當result.value是一個函數時,任務執行器會先執行這個函數再講結果傳入next()方法,代碼更新以下 :
function run(taskDef) { // 建立一個無使用限制的迭代器 let task = taskDef(); //開始執行任務 let result = task.next(); // 循環調用next()函數 function step() { //若是任務未完成 , 則繼續執行 if(!result.done) { if(typeof result.value === "function") { result.value(function(err,data) { if(err) { result = task.throw(err); return; } result = task.next(data); step(); }); } else { result = task.next(result.value); step(); } } } //開始執行迭代 step(); }
經過 === 操做符檢查後 , 若是 result.value 是一個函數,會傳入一個回調函數做爲參數來調用它,回調函數遵循node.js中有關執行錯誤的約定;所可能的錯誤放在第一個參數(err)中,結果放在第二個參數中。若是傳入了err,則意味着執行過程當中產生了cuow,這時會經過task.throw()正確輸出錯誤對象。若是 result.value 不是一個函數,則直接將其傳入next()方法。
如今,這個新版的任務執行器已經能夠用於全部的異步任務了。在node.js環境中,若是要從文件中讀取一些數據,須要在fs.readFile()外圍建立一個包裝器(wrapper), 返回一個與 fetchData()相似的函數
let fs = require('fs'); function readFile(filename) { return function(callback) { fs.readFile(filename,'utf8',callback); } } //調用 run(function *(){ let contents = yield readFile('1.txt'); console.log(contents); })
哎,咱們終於開始學習Promise了,接一來,咱們將學
javaScript引擎是基於單線程和事件循環的概念構建的,同一時刻只容許一個代碼塊執行。
那麼,若是咱們有一天必須控制 兩段異步操做的執行順序時,咱們該怎麼辦呢?
好比,咱們必須先 讀1.txt文件 , 再複製一份 2.txt文件。
咱們知道 , I/O操做都是異步的 , 控制順序的話,咱們能夠這樣:
fs.readFile('./1.txt','utf8',(err,data)=>{ fs.writeFile('./2.txt',data,(err)=>{ if(err) { return; } }) })
簡單來講,就是把寫的操做放在讀的裏面來執行。但這樣不太好,由於一旦要操做異步的順序的代碼多了起來,一旦不知道哪裏出了 bug ,就會很難調試,看的頭都會大。
Promise是一種異步操做的解決方案,將寫法複雜的傳統的回調函數和監聽事件的異步操做,用同步代碼的形式表達出來。避免了多級異步操做的回調函數嵌套。
Promise至關於異步操做結果的佔位符,它不會訂閱一個事件,也不會傳遞一個回調函數給目標函數,而是讓函數返回一個Promise
每一個Promise都會經歷一個短暫的生命週期 :先是處於進行中(pending)的狀態,此時操做還沒有完成,因此它也是未處理(unsettled)的;一但異步操做執行結束,Promise在變成下面其中一個:
note:若是一個對象實現了then()方法,那這個對象咱們稱之爲 thenable 對象。全部的Promise都是thenable對象,但並不是全部thenable對象都是Promise.
有了Promise,咱們即可以將上面的讀寫操做,變成下面這樣了:
//讀 function readPath(path) { return new Promise((resolve,reject)=>{ fs.readFile(path,'utf8',(error,data)=>{ if(error) { reject(error); } else { resolve(data) } }) }) } //寫 function wirtePath(path,data) { return new Promise((resolve,reject)=>{ fs.writeFile(path,data,(error)=>{ if(error) { reject(error) } else { resolve('success') } }) }) } readPath('./1.txt') .then((data)=>{ wirtePath('./2.txt',data); },(reason)=>{ console.log(reason); })
更好的異步任務執行: 其基本思想爲
用 async 標記的函數 代替生成器 , 用 await 代替 yield 來調用函數,就像這樣
async function (){ let content = await readPath('./1.txt'); let msg = await wirtePath('./2.txt',content); console.log(msg) }() // 這裏是自執行函數
咱們以爲與其看各類各樣的Promise面試題,都不如先實現一下Promise,明白了原理,天然更容易知道怎麼用 如今,咱們開始這篇文章的壓軸代碼,實現一個簡單的Promise,
首先,Promise確定是一個類,咱們就用 class 來聲明
因爲 resolve 和 reject 可執行 , 因此都是函數 , 咱們用let 聲明
class Promise {
constructor(executor) { // 成功 let resolve = (value) => {}; // 失敗 let reject = (reason) => {}; }
}
對Promise的規定
如果executor函數報錯 直接執行reject();
class Promise {
constructor(executor) { //初始化 state 爲等待狀態 this.state = 'pending'; // 成功的值 this.value = undefined; // 失敗的值 this.reason = undefined; let resolve = (value) => { // state 改變 , resolve 調用會失敗 if(this.state === 'pending') { this.state = 'fulfilled';// 調用 resolve後,state轉化爲成功狀態 this.value = value;// 存儲成功的值 } }; // 失敗 let reject = (reason) => { //state 改變後, reject 調用就會失敗 if(this.state === 'pending') { // reject 調用後, state 轉化爲失敗狀態 this.state = 'rejected'; this.reason = reason;// 存儲失敗的緣由 } }; // 當即執行 // 若是 executor 執行報錯 , 直接執行 reject try { executor(resolve,reject); } catch (err) { reject(err); } }
}
規定 : Promise有一個叫作then的方法 , 裏面有兩個參數 :onFulfilled , onRejected ,成功有成功的值 ,失敗有失敗的緣由
onFulfilled, onRejected 若是他們是函數, 則必須分別在 fulfilled , rejected 後被調用 , value 或 reason 依次做爲他們的第一個參數
then(onFulfilled,onRejected) {
// 狀態爲 fulfilled , 執行 onFulfilled , 傳入成功的值 if(this.state === 'fulfilled') { onFulfilled(this.value); }; // 狀態爲rejected , 執行onRejected , 傳入失敗的緣由 if(this.state === 'rejected') { onRejected(this.reason); }
}
如今基本能夠實現簡單的同步代碼 ,可是當 resolve在setTimeout 內執行 , then 時state仍是pending等待狀態 , 咱們就須要在 then調用的時候 , 將成功和失敗存到各自的數組 , 一旦 reject 或者 resolve ,就調用他們。
相似於發佈訂閱 , 先將then裏面的兩個函數存儲起來 , 因爲一個 Promise能夠有多個 then , 因此存在同一個數組內。
// 手寫Promise class Promise { constructor(executor) { //初始化狀態 this.state = 'pending'; // 成功值 this.value = undefined; // 失敗值 this.reason = undefined; // 成功存放的數組 this.onResolvedCallbacks = []; // 失敗存放的數組 this.onRejectedCallbacks = []; // 成功 let resolve = (value) => { // state 改變 , resolve 調用 if(this.state === 'pending') { this.state = 'fulfilled';// 調用 resolve 後 , state轉換爲成功狀態 this.value = value;//存儲成功的值 // 一旦resolve 執行, 調用成功數組的函數 this.onResolvedCallbacks.forEach(fn => fn()); } } // 失敗 let reject = (reason) => { if(this.state === 'pending') { this.state = 'rejected'; this.reason = reason; // 一旦 resolve執行 , 調用失敗數組的函數 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve,reject); } catch (error) { reject(error) } } then(onFulfilled,onRejected) { // 狀態爲 fulfilled , 執行 onFulfiiled , 傳入成功的值 if(this.state === 'fulfilled') { onFulfilled(this.value); }; //狀態爲 rejected , 執行onRejected , 傳入失敗的緣由 if(this.state === 'rejected') { onRejected(this.reason); } // 當狀態爲 pending時 if(this.state === 'pending') { // onFulfilled 傳入到成功數組中 this.onResolvedCallbacks.push(()=>{ onFulfilled(this.value); }) // onRejected 傳入到失敗數組 this.onRejectedCallbacks.push(()=>{ onRejected(this.reason); }) } } catch() { } }
咱們經常用到的 new Promise().then().then(),這就是鏈式調用,用來解決回調地獄的問題。
規定 : 在then裏面返回一個新的Promise , 稱爲promise2;
promise2 = new Promise((resolve,reject)=>{})
規定 : onFulfilled()或 onRejected()的值 , 即第一個then返回的值 , 叫作 x ,判斷 x 的函數叫作 resolvePromise
resolve 和 reject 是promise2的
class Promise {
constructor(executor) { //初始化狀態 this.state = 'pending'; // 成功值 this.value = undefined; // 失敗值 this.reason = undefined; // 成功存放的數組 this.onResolvedCallbacks = []; // 失敗存放的數組 this.onRejectedCallbacks = []; // 成功 let resolve = (value) => { // state 改變 , resolve 調用 if (this.state === 'pending') { this.state = 'fulfilled';// 調用 resolve 後 , state轉換爲成功狀態 this.value = value;//存儲成功的值 // 一旦resolve 執行, 調用成功數組的函數 this.onResolvedCallbacks.forEach(fn => fn()); } } // 失敗 let reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.reason = reason; // 一旦 resolve執行 , 調用失敗數組的函數 this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (error) { reject(error) } } then(onFulfilled, onRejected) { // 聲明返回的 promise2 let promise2 = new Promise((resolve, reject) => { // 狀態爲 fulfilled , 執行 onFulfiiled , 傳入成功的值 if (this.state === 'fulfilled') { let x = onFulfilled(this.value); // resolvePromise 函數 , 處理本身 return 的 promise和 默認的 promise2的關係 resolvePromise(promise2, x, resolve, reject); }; //狀態爲 rejected , 執行onRejected , 傳入失敗的緣由 if (this.state === 'rejected') { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } // 當狀態爲 pending時 if (this.state === 'pending') { // onFulfilled 傳入到成功數組中 this.onResolvedCallbacks.push(() => { let x = onFulfilled(this.value); resolvePromise(promise2,x,resolve,reject); }) // onRejected 傳入到失敗數組 this.onRejectedCallbacks.push(() => { let x = onRejected(this.reason); resolvePromise(promise2,x,resolve,reject); }) } }); // 返回promise2 , 完成鏈式 return promise2; }
}
規定 : 一段代碼 , 讓不一樣的promise代碼互相套用 , 叫作resolvePromise
若是 x === promise2 , 則是會形成循環引用 , 本身等待本身完成 ,則報 "循環引用" 錯誤。
function resolvePromise(promise2,x,resolve,reject) {
if(x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); }
}
note : 這個方法是 Promise 原碼的精髓 , 能夠仔細看看
// 完成 resolvePromise函數 function resolvePromise(promise2, x, resolve, reject) { // 循環引用報錯 if (x === promise2) { return reject(new TypeError('Chaining cycle detected for promise')); } //防止屢次調用 let called; // x不是null 而且 x是對象或者函數 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+ 規定 , 聲明 then = x 的 then 方法 let then = x.then; //若是 then 是函數 , 就默認是promise 了 if (typeof then === 'function') { // 就讓 then 執行 第一個參數是 this 後面是成功的回調 和失敗的回調 then.call(x, y => { // 成功和失敗只能調用一個 if (called) return; called = true; // resolve 就結果依舊是 promise 那就繼續解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能調用一個 if (called) return; called = true; reject(err); }) } else { resolve(x); // 直接成功便可 } } catch (e) { // 也屬於失敗 if(called) return; called = true; reject(e); } } else { resolve(x); } }
能夠 look a look
// 手寫Promise class Promise { constructor(executor) { // 初始化state 爲等待狀態 this.state = 'pending'; // 成功的值 this.value = undefined; // 失敗的值 this.reason = undefined; // 成功存放的數組 this.onResolvedCallbacks = []; // 失敗存放的數組 this.onRejectedCallbacks = []; // 成功 let resolve = (value) => { // state 改變 , resolve 調用的會失敗 if (this.state === 'pending') { this.state = 'fulfilled'; // resolve調用後 , state轉化爲成功態 this.value = value;// 存儲成功的值 // 一旦resolve執行 , 調用成功數組的函數 this.onResolvedCallbacks.forEach(fn => fn()); } } // 失敗 let reject = (reason) => { // state改變後 , reject調用就會失敗 if (this.state === 'pending') { // reject 調用後 , state 轉換爲失敗狀態 this.state = 'rejected'; this.reason = reason;// 存儲失敗的緣由 // 一旦reject 執行 , 調用失敗數組的函數 this.onRejectedCallbacks.forEach(fn => fn()); } }; // 當即執行 // 若是 executor 執行報錯 , 直接執行 reject try { executor(resolve, reject) } catch (err) { reject(err) } } then(onFulfilled, onRejected) { // onFulfilled 若是不是函數 , 就忽略 onFulfilled , 直接返回 value onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value; // onRejected 若是不是函數 , 就忽略 onRejected , 直接扔出錯誤 onRejected = typeof onRejected === "function" ? onRejected : err => { throw err }; // 聲明返回的 promise2 let promise2 = new Promise((resolve, reject) => { if (this.state === 'fulfilled') { // 異步 setTimeout(() => { try { let x = onFulfilled(this.value); // resolvePromise函數 , 處理本身return 的promise和默認的promise2的關係 resolvePromise(promise2, x ,resolve,reject); } catch (e) { reject(e); } },0) }; if (this.state === 'rejected') { //異步 setTimeout(() => { // 若是報錯 try { let x = onRejected(this.reason); resolvePromise(promise2,x,resolve,reject); } catch (e) { reject(e); } },0); }; // 當狀態state爲pending 時 if (this.state === 'pending') { // onFulfilled傳入到成功數組 this.onResolvedCallbacks.push(() => { //異步 setTimeout(() => { try { let x = onFulfilled(this.value); resolvePromise(promise2,x , resolve,reject); } catch (e) { reject(e) } },0); }) // onRejected 傳入到失敗數組 this.onRejectedCallbacks.push(() => { // 異步 setTimeout(() => { try { let x = onRejected(this.reason); resolvePromise(promise2,x,resolve,reject); } catch (e) { reject(e) } }) }) } }); // 返回promise , 完成鏈式 return promise2; } catch(fn) { return this.then(null,fn); } } function resolvePromise(promise2, x, resolve, reject) { if (x === promise2) { return reject(new TypeError("Chaining cycle detected for promise")); } // 防止屢次調用 let called; // x 不是 null 而且 x 是對象或者函數 if (x != null && (typeof x === 'object' || typeof x === 'function')) { try { // A+ 規定 , 聲明 then = x 的then 方法 let then = x.then; // 若是 then 是函數 , 就默認是 promise了 if (typeof then === 'function') { // 就讓 then 執行 第一個參數是 this 後面是成功的回調和 失敗的回調 then.call(x, y => { // 成功和失敗只能調用一個 if (called) return; called = true; // resolve 的結果依舊是 promise 那就繼續解析 resolvePromise(promise2, y, resolve, reject); }, err => { // 成功和失敗只能調用一個 if (called) return; called = true; reject(err); // 失敗了就失敗了 }) } else { resolve(x); //直接成功便可 } } catch (e) { // 也屬於失敗 if (called) return; called = true; // 取then出錯了 那就不要再繼續執行了 reject(e); } } else { resolve(x); } }
固然, 這裏還有一些掛在 Promise上的方法
// resolve方法 Promise.resolve = function(val) { return new Promise((resolve,reject)=>{ resolve(val) }) } // reject方法 Promise.reject = function(val) { return new Promise((resolve,reject)=> { reject(val); }) } // all 方法 (獲取全部的promise , 都執行then , 把結果放到數組 , 一塊兒返回, 若是其中有一個發送了reject , 則取出失敗的時候最早被reject失敗狀態的值) // 這個方法比較經常使用 , 通常用於 併發請求數據 ,ajax併發請求數據 Promise.all = function(promises) { let arr = []; let i = 0; functionData(index,data) { arr[index] = data; i++; if(i == promises.length) { resolve(arr); } } return new Promise((resolve,reject) => { for(let i = 0 ; i < promises.length ; i ++) { promises[i].then(data => { processData(i,data); },reject) } }) } // race方法 // mdn的解釋: Promise.race()一旦可迭代的promise中的一個承諾實現或拒絕,該方法就會返回一個承諾,該承諾中包含承諾的價值或緣由。 // 仍是比較好理解的 Promise.race = function(promises) { return new Promise((resolve,reject) =>{ for(let i = 0 ; i < promises.length ; i++) { promises[i].then(resolve,reject); } }) } // 用法大概是下面這樣了 let p1 = new Promise((resolve, reject) => { setTimeout(() => { resolve('成功了') }, 1000); }) let p2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('success') }, 900); }) Promise.race([p1, p2]).then((result) => { console.log(result) // success })