摘要:異步編程程序執行分為同步和異步,如果程序每執行一步都需要等待上一步完成才能開始,此所謂同步。因此異步編程十分重要。
異步編程
程序執行分為同步和異步,如果程序每執行一步都需要等待上一步完成才能開始,此所謂同步。如果程序在執行一段代碼的同時可以去執行另一段代碼,等到這段代碼執行完畢再吧結果交給另一段代碼,此所謂異步。
比如我們需要請求一個網絡資源,由于網速比較慢,同步編程就意味著用戶必須等待下載處理結束才能繼續操作,所以用戶體驗極為不好;如果采用異步,下載進行中用戶繼續操作,當下載結束了,告訴用戶下載的數據,這樣體檢就提升了很多。因此異步編程十分重要。
從計算機的角度來講,js 只有一個線程,如果沒有異步編程那一定會卡死的!異步編程主要包括以下幾種:
回調函數
事件監聽
發布/訂閱模型
Promise對象
ES6異步編程
回調函數 和 Promise回調函數應該是 js 中十分基礎和簡單的部分,我們在定義事件,在計時器等等使用過程中都使用過:
fs.readFile("/etc/passwd", function(err, data){ if(err) throw err; console.log(data); });
比如這里的這個文件讀取,定義了一個回調函數,在讀取文件成功或失敗是調用,并不會立刻調用。
如同之前在 Promise 中提到的,當我想不斷的讀入多個文件,就會遇到回調函數嵌套,書寫代碼及其的不方便,我們稱之為"回調地獄"。因此 ES6 中引入是了 Promise 解決這個問題。具體表現參看之前的 Promise 部分。但是 Promise 也帶來了新的問題,就是代碼冗余很嚴重,一大堆的 then 使得回調的語義不明確。
協程所謂協程就是幾個程序交替執行:A開始執行,執行一段時間后 B 執行,執行一段時間后再 A 繼續執行,如此反復。
function* asyncJob(){ //... var f = yield readFile(fileA); //... }
通過一個 Generator 函數的 yield, 可以將一個協程中斷,去執行另一個協程。我們可以換一個角度理解 Generator 函數:它是協程在 ES6 中的具體體現。我們可以簡單寫一個異步任務的封裝:
var fetch = require("node-fetch"); function* gen(){ var url = "http://api.github.com/users/github"; var result = yield fetch(url); console.log(result.bio); } var g = gen(); var result = g.next(); //返回的 value 是一個 Promise 對象 result.value.then(function(data){ return data.json; }).then(function(data){ g.next(data); });Thunk 函數
在函數傳參數時我們考慮這樣一個問題:
function fun(x){ return x + 5; } var a = 10; fun(a + 10);
這個函數返回25肯定沒錯,但是,我們傳給函數 fun 的參數在編譯時到底保留 a + 10 還是直接傳入 20?顯然前者沒有事先計算,如果函數內多次使用這個參數,就會產生多次計算,影響性能;而后者事先計算了,但如果函數里不使用這個變量就白浪費了性能。采用把參數原封不動的放入一個函數(我們將這個函數稱為 Thunk 函數),用的使用調用該函數的方式。也就是上面的前一種方式傳值。所以上面代碼等價于:
function fun(x){ return x() + 5; } var a = 10; var thunk = function(){ return a + 10}; fun(thunk);
但是 js 不是這樣的!js 會把多參數函數給 Thunk 了,以減少參數:
var fs = require("fs"); fs.readFile(fileName, callback); var readFileThunk = Thunk(fileName); readFileThunk(callback); var Thunk = function(fileName){ return function(callback){ return fs.readFile(fileName,callback); }; };
這里任何具有回調函數的函數都可以寫成這樣的 Thunk 函數,方法如下:
function Thunk(fn){ return function(){ var args = Array.prototype.slice.call(arguments); return function (callback){ args.push(callback); return fn.apply(this, args); } } } //這樣fs.readFile(fileName, callback); 寫作如下形式 Thunk(fs.readFile)(fileName)(callback);
關于 Thunk 函數, 可以直接使用 thunkify 模塊:
npm install thunkify
使用格式和上面的Thunk(fs.readFile)(fileName)(callback);一致,但使用過程中需要注意,其內部加入了檢查機制,只允許 callback 被回調一次!
結合 Thunk 函數和協程,我們可以實現自動流程管理。之前我們使用 Generator 時候使用 yield 關鍵字將 cpu 資源釋放,執行移出 Generator 函數。可以怎么移回來呢?之前我們手動調用 Generator 返回的迭代器的 next() 方法,可這畢竟是手動的,現在我們就利用 Thunk 函數實現一個自動的:
var fs = require("fs"); var thunkify = require("thunkify"); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路徑數組 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(fn){ var gen = fn(); function next(err, data){ if(err) throw err; var result = gen.next(data); if(result.done) return; //遞歸直到所以文件讀取完成 result.value(next); //遞歸執行 } next(); })(gen); //之后可以使用 run 函數繼續讀取其他文件操作
如果說 Thunk 可以有現成的庫使用,那么這個自動執行的 Generator 函數也有現成的庫可以使用——co模塊(https://github.com/tj/co)。用法與上面類似,不過 co 模塊返回一個 Promise 對象。使用方式如下:
var co = require("co"); var fs = require("fs"); var thunkify = require("thunkify"); var readFile = thunkify(fs.readFile); var gen = function*(...args){ //args 是文件路徑數組 for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; co(gen).then(function(){ console.log("files loaded"); }).catch(function(err){ console.log("load fail"); });
這里需要注意的是:yield 后面只能跟一個 thunk 函數或 promise 對象。上例中第8行 yield 后面的 readFile 是一個 thunk 函數,所以可以使用。
上面已經講解了 thunk 函數實現自動流程管理,下面使用 Promise 實現一下:
var fs = require("fs"); var readFile = function(fileName){ return new Promise(function(resolve, reject){ fs.readFile(fileName, function(error,data){ if(error) reject(error); resolve(data); }); }); }; var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } }; (function run(gen){ var g = gen(); var resolve = function(data){ var result = g.next(data); if(result.done) return result.value; result.value.then(resolve); } g.next().value.then(function(data){ resolve(data); }); resolve(); })(gen); //之后可以使用 run 函數繼續讀取其他文件操作async 函數
ES7 中提出了 async 函數,但是現在已經可以用了!可這個又是什么呢?其實就是 Generator 函數的改進,我們上文寫過一個這樣的 Generator 函數:
var gen = function*(){ for(var i = 0, len = args.length; i < len; i++){ var r = yield readFile(args[i]); console.log(r.toString()); } };
我們把它改寫成 async 函數:
var asyncReadFiles = async function(){ //* 替換為 async for(var i = 0, len = args.length; i < len; i++){ var r = await readFile(args[i]); //yield 替換為 await console.log(r.toString()); } };
async 函數對 Generator 函數做了一下改進:
Generator 函數需要手動通過返回值的 next 方法執行,而 async 函數自帶執行器,執行方式和普通函數完全一樣。
var result = asyncReadFiles(fileA, fileB, fileC);
語義明確,async 表示異步,await 表示后續表達式需要等待觸發的異步操作結束
co 模塊中 yield 后面只能跟一個 thunk 函數或 promise 對象,而 await 后面可以是任何類型(不是 Promise 對象就同步執行)
返回值是一個 Promise 對象,不是 Iterator ,比 Generator 方便
我們可以實現這樣的一個 async 函數:
async function asyncFun(){ //code here } //equal to... function asyncFun(args){ return fun(function*(){ //code here... }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
我們使用 async 函數做點簡單的事情:
function timeout(ms){ return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function delay(nap, ...values){ while(1){ try{ await timeout(nap); } catch(e) { console.log(e); } var val = values.shift(); if(val) console.log(val) else break; } } delay(600,1,2,3,4); //每隔 600ms 輸出一個數
這里需要注意:應該把后面跟 promise對象的 await 放在一個 try 中,防止其被 rejected。當然上面的 try 語句也可以這樣寫:
var ms = await timeout(nap).catch((e) => console.log(e));
對于函數參數中的回調函數不建議使用,避免出現不應該的錯誤
//反例: 會得到錯誤結果 async function fun(db){ let docs = [{},{},{}]; docs.forEach(async function(doc){ //ReferenceError: Invalid left-hand side in assignment await db.post(doc); }); } //改寫, 但依然順序執行 async function fun(db){ let docs = [{},{},{}]; for(let doc of docs){ await db.post(doc); } } //改寫, 并發執行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = await Promise.all(promises) console.log(result); } //改寫, 并發執行 async function fun(db){ let docs = [{},{},{}]; let promises = docs.map((doc) => db.post(doc)); let result = []; for(let promise of promises){ result.push(await promise); } console.log(result); }Promise,Generator 和 async 函數比較
這里我們實現一個簡單的功能,可以直觀的比較一下。實現如下功能:
在一個 DOM 元素上綁定一系列動畫,每一個動畫完成才開始下一個,如果某個動畫執行失敗,返回最后一個執行成功的動畫的返回值
Promise 方法
function chainAnimationPromise(ele, animations){ var ret = null; //存放上一個動畫的返回值 var p = Promise.resolve(); for(let anim of animations){ p = p.then(function(val){ ret = val; return anim(ele); }); } return p.catch(function(e){ /*忽略錯誤*/ }).then(function(){ return ret; //返回最后一個執行成功的動畫的返回值 }); }
Generator 方法
function chainAnimationGenerator(ele, animations){ return fun(function*(){ var ret = null; try{ for(let anim of animations){ ret = yield anim(ele); } } catch(e) { /*忽略錯誤*/ } return ret; }); function fun(genF){ return new Promise(function(resolve, reject){ var gen = genF(); function step(nextF){ try{ var next = nextF(); } catch(e) { return reject(e); } if(next.done){ return resolve(next.value); } Promise.resolve(next.value).then(function(data){ step(function(){ return gen.next(data); }); }, function(e){ step(function(){ return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); }); } }
async 函數方法
async function chainAnimationAsync(ele, animations){ var ret = null; try{ for(let anim of animations){ ret = await anim(elem); } } catch(e){ /*忽略錯誤*/ } return ret; }一個經典題
console.log(0); setTimeout(function(){ console.log(1) },0); setTimeout(function(){ console.log(2); },1000); var pro = new Promise(function(resolve, reject){ console.log(3); resolve(); }).then(resolve => console.log(4)); console.log(5); setTimeout(function(){ console.log(6) },0); pro.then(resolve => console.log(7)); var pro2 = new Promise(function(resolve, reject){ console.log(8); resolve(10); }).then(resolve => console.log(11)) .then(resolve => console.log(12)) .then(resolve => console.log(13)); console.log(14); // 0 3 5 8 14 4 11 7 12 13 1 6 2
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97441.html
摘要:函數可以沒有返回值,此時它依然返回一個并且在調用方法時一次行執行完函數內全部代碼,返回。將一個可遍歷結構解構,并逐一返回其中的數據。 Generator Generator 函數是 es6 中的新的異步編程解決方案,本節僅討論 Generator 函數本身,異步編程放在后面的部分。Generator 函數之前也提到過,描述內部封裝的多個狀態,類似一個狀態機,當然也是很好的 iterat...
摘要:不推薦移動端瀏覽器前端優化策略相對于桌面端瀏覽器,移動端瀏覽器上有一些較為明顯的特點設備屏幕較小新特性兼容性較好支持一些較新的和特性需要與應用交互等。 GitHub鏈接:https://github.com/zwwill/blo... 圍繞前端的性能多如牛毛,涉及到方方面面,以我我們將圍繞PC瀏覽器和移動端瀏覽器的優化策略進行羅列注意,是羅列不是展開,遇到不會不懂的點還請站外擴展 開車...
摘要:不推薦移動端瀏覽器前端優化策略相對于桌面端瀏覽器,移動端瀏覽器上有一些較為明顯的特點設備屏幕較小新特性兼容性較好支持一些較新的和特性需要與應用交互等。 GitHub鏈接:https://github.com/zwwill/blo... 圍繞前端的性能多如牛毛,涉及到方方面面,以我我們將圍繞PC瀏覽器和移動端瀏覽器的優化策略進行羅列注意,是羅列不是展開,遇到不會不懂的點還請站外擴展 開車...
摘要:不推薦移動端瀏覽器前端優化策略相對于桌面端瀏覽器,移動端瀏覽器上有一些較為明顯的特點設備屏幕較小新特性兼容性較好支持一些較新的和特性需要與應用交互等。 GitHub鏈接:https://github.com/zwwill/blo... 圍繞前端的性能多如牛毛,涉及到方方面面,以我我們將圍繞PC瀏覽器和移動端瀏覽器的優化策略進行羅列注意,是羅列不是展開,遇到不會不懂的點還請站外擴展 開車...
摘要:對象是工作組為異步編程提供的統一接口,是中提供了對的原生支持,就是在未來發生的事情,使用可以避免回調函數的層層嵌套,還提供了規范更加容易的對異步操作進行控制。是執行完之后的回調,可以用方法分別指定和的回調。 Promise對象是CommonJS工作組為異步編程提供的統一接口,是ECMAScript6中提供了對Promise的原生支持,Promise就是在未來發生的事情,使用Promis...
閱讀 3092·2021-10-12 10:20
閱讀 2824·2021-09-27 13:56
閱讀 799·2021-09-27 13:36
閱讀 1439·2021-09-26 09:46
閱讀 2425·2019-08-30 14:02
閱讀 2693·2019-08-28 18:14
閱讀 1270·2019-08-26 10:32
閱讀 1712·2019-08-23 18:25