簡單來講就是一個任務分紅多個步驟執行,先執行某一段任務,跳出轉而執行其餘任務, 等下一段任務準備完成後, 轉而回來執行下一段任務前端
像這種類型, 把一個任務分解成多段任務 不連續 執行, 就叫作異步, 連續執行的則叫作同步node
經過拆解一個任務, 分紅多段,把第二段任務單獨寫在第二個函數內,等到須要執行這個任務時, 直接調用這個函數golang
node.js中經常使用的就是如此方法。ajax
fs.readFile('某個文件', function (err, data) { if (err) throw err; console.log(data); });
這是一個錯誤優先的回調函數(error-first callbacks),這也是Node.js自己的特色之一。 相似golang中的err 錯誤處理編程
回調帶來一些問題, 第一個就是多層嵌套問題, 當一個問題很複雜, 多段不連續, 就會出現地獄嵌套問題數組
fs.readFile('某個文件', function (err, data) { if (err) throw err; fs.writeFile('某個文件',data, function (err, data) { if (err) throw err; fs.readFile('某個文件', function (err, data) { if (err) throw err; console.log("寫入的是:",data) }); }); });
沒法使用try{}catch(){} 捕獲錯誤
列子:promise
try{ setTimeout(()=>{ callback() throw new Error('拋出錯誤') },1000) }.catch(err){ console.log('看看是否走到了這裏') }
上面的代碼是沒法走到catch內部的, 因爲try{}catch 只能捕獲當前任務循環內的任務拋出錯誤, 而這個回調被存放起來, 直到下一個事件環的時候纔會取出, try{}catch實在無能爲力app
在node中,已約定回調的第一個參數是拋出的異常。只是用另外的方式來捕獲錯誤。
僞代碼異步
let func = function(callback){ try{ setTimeout(()=>{ if(success){ callback(null) }else{ callback(new Error('錯誤')) } },1000) }catch(e){ console.log('捕獲錯誤',e); } }
一般在前端操做的通常是經過addeventLisener監聽各類事件,好比鍵盤事件 鼠標事件等等,async
document.addeventListener('click',function(e){ console.log(e.target) },false)
一般把須要執行的任務先暫存起來, 等達到條件或者發佈的時候一一拿出來執行
class Task{ construct(){ this.tasks = {} } publish(event){ this.tasks[event].forEach(fn=>fn()) } subscribe(event,eventTask){ this.tasks[event] = this.tasks[event] ? this.tasks[event] : [] this.tasks[event].push(eventTask) } } let task = new Task() task.subscribe('eat',function(){console.log('吃午餐')}) task.subscribe('eat',function(){console.log('吃晚飯')}) task.publish('eat')
Promise/Deferred模式
生成器Generators/ yield
function* foo () { var index = 0; while (index < 2) { yield index++; //暫停函數執行,並執行yield後的操做 } } var bar = foo(); // 返回的實際上是一個迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
yield是一個表達式, 後面緊跟着的表達式是next()的返回結果的value,而若是想給yield傳遞參數,好比a=yield 1,給a傳遞值 則要next(value))
// 例子: function* foo () { a = yield 1 console.log(a) // 10 } var bar = foo(); // 返回的實際上是一個迭代器 console.log(bar.next()); // { value: 1, done: false } console.log(bar.next(10)); // { value: undefined, done: true }
能夠理解爲yield有兩步操做,第一個next彈出值,第二個next接收值而且執行一下段語句,直到下一個yield彈出值爲止
function* iterArr(arr) { //迭代器返回一個迭代器對象 if (Array.isArray(arr)) { // 內節點 for(let i=0; i < arr.length; i++) { yield* iterArr(arr[i]); // (*)遞歸 } } else { // 離開 yield arr; } } var arr = [ 'a', ['b',[ 'c', ['d', 'e']]]]; var gen = iterArr(arr); arr = [...gen];
function* main(){ try{ let result = yield foo() console.log(result) }catch(e){ console.log(e) } } let it = main() function foo(params,url){ $.ajax('www.baidu.com', function(err,data){ if(err){ it.throw(err) }else{ it.next(data) } } } it.next() // {value:undefind,done:false}
Promise 對象用於表示一個異步操做的最終完成 (或失敗), 及其結果值.
他有三個狀態 分別是pending resolved 以及rejected
一旦發生狀態改變, 就不可再更改了 每次then都另外建立一個promise對象
// 僞代碼 class Promise{ construct(executor){ let self = this // 一個 Promise有如下幾種狀態: // pending: 初始狀態,既不是成功,也不是失敗狀態。 // fulfilled: 意味着操做成功完成。 // rejected: 意味着操做失敗。 this.status = 'pending' this.res = undefined // 存成功以後的值 this.err = undefined // 存失敗以後的值 this.onFulfilledCallback = [] this.onRejectedCallback = [] function resolve(res){ if(self.status === 'pending'){ self.status = 'resolved' self.res = res onFulfilledCallback.forEach(fn=>fn()) } } function reject(err){ if(self.status === 'pending'){ self.status = 'rejected' self.err = err onRejectedCallback.forEach(fn=>fn()) } } // executor是帶有 resolve 和 reject 兩個參數的函數 。Promise構造函數執行時當即調用executor 函數, resolve 和 reject 兩個函數做爲參數傳遞給executor(executor 函數在Promise構造函數返回所建promise實例對象前被調用)。resolve 和 reject 函數被調用時,分別將promise的狀態改成fulfilled(完成)或rejected(失敗)。executor 內部一般會執行一些異步操做,一旦異步操做執行完畢(可能成功/失敗),要麼調用resolve函數來將promise狀態改爲fulfilled,要麼調用reject 函數將promise的狀態改成rejected。若是在executor函數中拋出一個錯誤,那麼該promise 狀態爲rejected。executor函數的返回值被忽略。 executor(resolve,reject) } then(onFulfilled,onRejected){ let self = this return new Promise((resolve,reject)=>{ if(self.status === 'resolved'){ let x = onFulfilled(self.res) // 拿到onFulfilled的執行結果 注意:這裏執行的是Promise.resolve() 同步代碼 // 而後把x傳遞給下一個then resolve(x) } if(self.status === 'rejected'){ let x = onRejected(self.res) // 拿到onFulfilled的執行結果 注意:這裏執行的是Promise.resolve() 同步代碼 // 而後把x傳遞給下一個then reject(x) } if(self.status === 'pending'){ self.onFulfilledCallback.push(function(){ let x = onFulfilled(self.res) // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 至關於 fn(){let x = onFulfilled} resolve(x) }) self.onRejectedCallback.push(function(){ let x = onRejected(self.res) // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 至關於 fn(){let x = onFulfilled} reject(x) }) } }) } }
function request(){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve({data:'得到數據'}) },1000) }) } request().then(data=>{ console.log(data) })
連接:Promise
function foo(x,y) { return request( "http://some.url.1/?x=" + x + "&y=" + y ); } function *main() { try { var text = yield foo( 11, 31 ); console.log( text ); } catch (err) { console.error( err ); } } let it = main() it.next()
var text = yield foo( 11, 31 )跟async await 是否是很像?
生成器能夠 yield 一個 promise,而後這個 promise 能夠被綁定,用其完成值來恢復這個生成器的運行。
// 僞代碼 function run(gen){ // 參數是一個gen函數 let it = gen.apply(this) return Promise.resolve().then((value)=>{ let next = it.next(value) return (function nextHandle(next){ if(next.done === true){ return next.value }else{ return Promise.resolve(next.value).then(nextHandle) // 遞歸 } })(next) }) } function* main(){ function *main() { try { var text = yield foo( 11, 31 ); console.log( text ); } catch (err) { console.error( err ); } } } run(main).then((data)=>{ // do something })
AsyncFunction 構造函數用來建立新的 異步函數 對象,JavaScript 中每一個異步函數都是 AsyncFunction 的對象。
注意,AsyncFunction 並非一個全局對象,須要經過下面的方法來獲取
Object.getPrototypeOf(async function(){}).constructor
語法:new AsyncFunction([arg1[, arg2[, ...argN]],] functionBody)
var AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; var a = new AsyncFunction('a', 'b', 'return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b);'); a(10, 20).then(v => { console.log(v); // 4 秒後打印 30 });
可是上面這種方式不高效 由於經過字面量建立的異步函數是與其餘代碼一塊兒被解釋器解析的,而new這種方式的函數體是單獨解析的。
async a(a,b){ return await resolveAfter2Seconds(a) + await resolveAfter2Seconds(b) }
await 表達式會暫停當前 async function 的執行,等待 Promise 處理完成。若 Promise 正常處理(fulfilled),其回調的resolve函數參數做爲 await 表達式的值,繼續執行 async function。
若 Promise 處理異常(rejected),await 表達式會把 Promise 的異常緣由拋出。
另外,若是 await 操做符後的表達式的值不是一個 Promise,則返回該值自己。
function resolveAfter2Seconds(x) { return new Promise(resolve => { setTimeout(() => { resolve(x); }, 2000); }); } async function f1() { var x = await resolveAfter2Seconds(10); console.log(x); // 10 } f1();