摘要:也就是說,你調(diào)用生成器函數(shù),它會(huì)返回給你一個(gè)迭代器。迭代器會(huì)遍歷每個(gè)中斷點(diǎn)。等同于總結(jié)異步解決方案還有其他的一些方法不過都不重要我們只要掌握了用寫異步代碼更方便維護(hù)第一次寫文章寫的不好多多包涵畢竟很多東西都是站在前任人的肩膀上直接拿過來的
由于JavaScript是單線程的一門腳本語言(主線程是單線程)
所以異步問題是個(gè)讓人常頭疼的問題
我們來看一下常見的傳統(tǒng)解決方案
1.回調(diào)函數(shù)回調(diào)函數(shù)是一種最常見 最傳統(tǒng)的方式 類似的這種
// node 的文件讀取 let fs = require("fs"); fs.readFile("./test1.js","utf8",function(err,data){ console.log(data) })
這樣我們可以在回調(diào)函數(shù)里拿到文件的內(nèi)容,然而這樣有一個(gè)問題, 要是我要讀取多個(gè)文件,每一個(gè)讀取的文件都要依賴前一個(gè)讀取文件的內(nèi)容
比如 我test1.js的內(nèi)容是test2的路徑
那么就要這樣寫
let fs = require("fs"); fs.readFile("./test1.js","utf8",function(err,data){ fs.readFile(data,"utf8",function(err,data){ console.log(data) }) })
要是100個(gè) 1000個(gè)文件呢 ? 由于異步調(diào)用無法用try{}catch 捕獲 萬一中間讀取失敗了一次又該怎么做? 難道每個(gè)函數(shù)體內(nèi)都if(err) 一下? 這種方式難以維護(hù) 也就是我們常說的回調(diào)地獄
2訂閱發(fā)布模式我現(xiàn)在有這樣一種需求 我需要在不同的文件里讀取不同的內(nèi)容, 等多個(gè)文件的內(nèi)容都讀取完畢再一起輸出
let fs = require("fs"); let result = {}; fs.readFile("./test1.js","utf8",function(err,data){ result.test1 = data fs.readFile("./test2","utf8",function(err,data){ result.test2 = data console.log(result) }) })
用回調(diào)方式會(huì)帶來什么問題? 需求: 這些異步請(qǐng)求沒有依賴關(guān)系 我需要同時(shí)發(fā)起 而不是等待上一次讀取的結(jié)果
現(xiàn)在我們來聊聊 訂閱發(fā)布模式
訂閱發(fā)布模式定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)訂閱者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在自身狀態(tài)變化時(shí),會(huì)通知所有訂閱者對(duì)象,使它們能夠自動(dòng)更新自己的狀態(tài)。 通俗點(diǎn)就事說, 我把我要操作的事放入一個(gè)待執(zhí)行的隊(duì)列里, 等達(dá)到某一個(gè)條件,待執(zhí)行隊(duì)列依次執(zhí)行,那么上代碼
let fs = require("fs"); let result = {}; class Publish { constructor() { this.list = [] }; on(fn){ this.list.push(fn) }; emit(string){ alert(string) if (Object.keys(result).length == 2) { this.list.forEach(fn => { fn() }) } } } let p = new Publish() p.on(function () { console.log(result) }) fs.readFile("./test1.js", "utf8", function (err, data) { result.test1 = data p.emit("已經(jīng)讀取到test1的文件") }) fs.readFile("./test2", "utf8", function (err, data) { result.test2 = data p.emit("已經(jīng)讀取到test2的文件") })
原理其實(shí)也就是回調(diào)函數(shù)
問題:發(fā)布訂閱跟觀察者模式有什么區(qū)別??
3 Promise好在我們有了Promise這個(gè)類 關(guān)于Promise的文章有很多 大家自行可以搜索一下
我們來看下Promise A+ 規(guī)范
那根據(jù)這個(gè)規(guī)范我們簡(jiǎn)單的寫一遍promise的源碼吧
我們來定義2個(gè)文件
Promise.js和require.js
//require.js let Promise = require("./promise.js") let p = new Promise((resolve, reject) => { setTimeout(()=>{ resolve(100) },100) }) p.then(function(data){ console.log(data) },function(e){ console.log(e) })
//promise.js class Promise { constructor(executor){ // promise的三個(gè)狀態(tài) this.state = "pending" this.value = undefined this.reason = undefined // 有可能調(diào)用then的時(shí)候 并沒有resolve或者reject 所以這里用來存放then之后要做的事 this.onResolvedCallbacks = [] this.onRejectedCallbacks = [] const resolve = (value) => { // 我們需要判斷resolve出來的值是否還是一個(gè)promise if(value instanceof Promise){ return value.then(resolve,reject) } // promiseA+ 規(guī)范要求這么寫 setTimeout(()=>{ if (this.state === "pending") { this.state = "resolved" this.value = value // 把保存起來的函數(shù)一一執(zhí)行然后結(jié)果傳給下一個(gè) this.onResolvedCallbacks.forEach( fn => { return fn(value) }) } }) } const reject = (reason) => { setTimeout(()=>{ if (this.state === "pending") { this.state = "rejected" this.reason = reason this.onRejectedCallbacks.forEach(fn => { return fn(reason) }) } }) } try { executor(resolve,reject) } catch (error) { reject(error) } } then(onFulfilled,onRejected){ // new 的時(shí)候馬上執(zhí)行executor ----> 就是(resolve,reject)=>{ }() 拿到resolve跟reject 然后做狀態(tài)判斷該調(diào)用哪個(gè) onFulfilled = typeof onFulfilled == "function" ? onFulfilled : function (value) { return value }; onRejected = typeof onRejected == "function" ? onRejected : function (value) { throw value }; let promise2 = new Promise((resolve,reject)=>{ if (this.state === "resolved"){ //onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1]. // 規(guī)范上要這么做 防止直接resolve 同步調(diào)用then 這個(gè)時(shí)候promise2不存在報(bào)錯(cuò) // 執(zhí)行順序參考瀏覽器事件環(huán) 哪天有空多帶帶寫一篇 setTimeout(()=>{ // 因?yàn)閛nFulfilled都是異步調(diào)用 所以不能在new Promise的時(shí)候捕獲到 try { let x = onFulfilled(this.value) // then成功的回調(diào) resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === "rejected"){ setTimeout(() => { try { let x = onRejected(this.reason) // 失敗的回調(diào) resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } if (this.state === "pending"){ // 如果executor是個(gè)異步方法 那么會(huì)先調(diào)用then 所以這里把成功回調(diào)跟失敗的回調(diào)都存起來 this.onResolvedCallbacks.push((value)=>{ try { let x = onFulfilled(value) resolvePromise(promise2, x, resolve, reject) } catch (error) { console.log(error) reject(error) } }) this.onRejectedCallbacks.push((reason)=>{ try { let x = onRejected(reason) resolvePromise(promise2, x, resolve, reject) } catch (error) { reject(error) } }) } }) // then返回一個(gè)promise return promise2 } catch(onRejected){ return this.then(null, onRejected); } static all(promises){ return new Promise(function (resolve, reject) { let result = []; let count = 0; for (let i = 0; i < promises.length; i++) { promises[i].then(function (data) { result[i] = data; if (++count == promises.length) { resolve(result); } }, function (err) { reject(err); }); } }); } } const resolvePromise = (promise2, x, resolve, reject)=>{ // promise2 跟 then的成功回調(diào)返回值有可能是同一個(gè)值 if(promise2 === x){ return reject(new TypeError("報(bào)錯(cuò) 循環(huán)引用了")) } let then,called; // 要么對(duì)象要么函數(shù) if(x !== null&&((typeof x === "object" || typeof x === "function")) ){ try { then = x.then // 有可能是getter定義的會(huì)報(bào)錯(cuò) // then 有可能是個(gè)函數(shù)或者普通值 if(typeof then === "function"){ // 如果then是個(gè)函數(shù)的話 就認(rèn)為它是個(gè)promise then.call(x,function(){ if(called) return called = true resolvePromise(promise2, y, resolve, reject); },function(error){ if (called) return called = true reject(error) }) }else{ resolve(x) } } catch (error) { if (called) return called = true reject(error) } }else{ // x是個(gè)普通值 resolve(x) } } module.exports = Promise
執(zhí)行require.js的結(jié)果是
這樣我們就實(shí)現(xiàn)了一個(gè)promise 是不是很棒棒? 現(xiàn)在我們可以promise.then 鏈?zhǔn)秸{(diào)用了 然后用catch做統(tǒng)一錯(cuò)誤處理 解決了上面錯(cuò)誤捕獲的問題 還有沒有更好的方法? 當(dāng)然有!
4 生成器 迭代器這篇文章講的比較詳細(xì):迭代器
在講async await 之前 我們先講一下 生成器
**當(dāng)你在執(zhí)行一個(gè)函數(shù)的時(shí)候,你可以在某個(gè)點(diǎn)暫停函數(shù)的執(zhí)行,并且做一些其他工作,然后再返回這個(gè)函數(shù)繼續(xù)執(zhí)行, 甚至是攜帶一些新的值,然后繼續(xù)執(zhí)行。
上面描述的場(chǎng)景正是JavaScript生成器函數(shù)所致力于解決的問題。當(dāng)我們調(diào)用一個(gè)生成器函數(shù)的時(shí)候,它并不會(huì)立即執(zhí)行, 而是需要我們手動(dòng)的去執(zhí)行迭代操作(next方法)。也就是說,你調(diào)用生成器函數(shù),它會(huì)返回給你一個(gè)迭代器。迭代器會(huì)遍歷每個(gè)中斷點(diǎn)。
next 方法返回值的 value 屬性,是 Generator 函數(shù)向外輸出數(shù)據(jù);next 方法還可以接受參數(shù)
function* foo () { var index = 0; while (index < 2) { yield index++; //暫停函數(shù)執(zhí)行,并執(zhí)行yield后的操作 } } var bar = foo(); // 返回的其實(shí)是一個(gè)迭代器 console.log(bar.next()); // { value: 0, done: false } console.log(bar.next()); // { value: 1, done: false } console.log(bar.next()); // { value: undefined, done: true }
Generator函數(shù)的標(biāo)志就是function關(guān)鍵詞后連綴一個(gè)"*" 配合yield 暫停函數(shù) 返回的是一個(gè)迭代器 每次執(zhí)行next的時(shí)候 停在yield
我們都見過類數(shù)組結(jié)構(gòu)吧
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3 } let arr = [...likeArray] //執(zhí)行這段代碼會(huì)報(bào)錯(cuò) 報(bào)錯(cuò)信息likeArray is not iterable likeArray是不可枚舉的 那么我們?nèi)绻雽?shí)現(xiàn)這樣的類數(shù)組轉(zhuǎn)為數(shù)組 怎么辦呢
我們先看一下函數(shù)里的argument跟類數(shù)組有什么區(qū)別
function(){ console.log(argument)}
我們改下一下類數(shù)組結(jié)構(gòu)
let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ return { next() { return { value: 1, done: false } } } } } //在執(zhí)行 let arr = [...likeArray] 控制臺(tái)報(bào)錯(cuò)FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 感覺是不是有點(diǎn)像那么回事了 我們?cè)俑膶?let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator](){ let index = 0 let self = this return { next() { return { done: self.length === index value: self[index++], } } } } } // 輸出[1,2,3] 只有在done是false的時(shí)候表示迭代完成 就不再繼續(xù)執(zhí)行了 value是每次迭代返回的值 再改寫一下 let likeArray = { 0: 1, 1: 2, 2: 3, length: 3, [Symbol.iterator]:function*() { let index = 0; while (index !== this.length) { yield this[index++] } } } console.log([...likeArray]) //[1,2,3] 調(diào)用返回一個(gè)迭代器 ... 每次調(diào)用迭代器的next方法 返回{value,done}
生成器可以配合node.js中的co, 借助于Promise,你可以使用更加優(yōu)雅的方式編寫非阻塞代碼。
例子:
let fs = require("fs"); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, function (err, data) { if (err) reject(err); else resolve(data); }) }) } function *read() { let template = yield readFile("./template.txt"); let data = yield readFile("./data.txt"); return template + "+" + data; } co(read).then(function (data) { console.log(data); }, function (err) { console.log(err); });5 async/await
有了上面的基礎(chǔ) async/await 更加容易明白了
async/await的優(yōu)點(diǎn)有
1.內(nèi)置執(zhí)行器
2.更好的語義
3.更廣的適用性
let fs = require("fs"); function readFile(filename) { return new Promise(function (resolve, reject) { fs.readFile(filename, "utf8", function (err, data) { if (err) reject(err); else resolve(data); }) }) } async function read() { let template = await readFile("./template.txt"); let data = await readFile("./data.txt"); return template + "+" + data; } let result = read(); result.then(data=>console.log(data));
可以直接await 一個(gè)promise 使得異步代碼執(zhí)行看起來像同步一樣 更優(yōu)雅
async 函數(shù)的實(shí)現(xiàn),就是將 Generator 函數(shù)和自動(dòng)執(zhí)行器,包裝在一個(gè)函數(shù)里。
async function read() { let template = await readFile("./template.txt"); let data = await readFile("./data.txt"); return template + "+" + data; } // 等同于 function read(){ return co(function*() { let template = yield readFile("./template.txt"); let data = yield readFile("./data.txt"); return template + "+" + data; }); }
**總結(jié): 異步解決方案還有其他的一些方法 不過都不重要 我們只要掌握了async/await 用async/await寫異步代碼 更方便維護(hù)
第一次寫文章 寫的不好多多包涵 畢竟很多東西都是站在前任人的肩膀上直接拿過來的**
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/99566.html
摘要:的翻譯文檔由的維護(hù)很多人說,阮老師已經(jīng)有一本關(guān)于的書了入門,覺得看看這本書就足夠了。前端的異步解決方案之和異步編程模式在前端開發(fā)過程中,顯得越來越重要。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書的目的是以目前還在制定中的ECMASc...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。寫一個(gè)符合規(guī)范并可配合使用的寫一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問題描述 在開發(fā)過程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過...
摘要:異步編程解決方案筆記最近讀了樸靈老師的深入淺出中異步編程一章,并參考了一些有趣的文章。另外回調(diào)函數(shù)中的也失去了意義,這會(huì)使我們的程序必須依賴于副作用。 JavaScript 異步編程解決方案筆記 最近讀了樸靈老師的《深入淺出NodeJS》中《異步編程》一章,并參考了一些有趣的文章。在此做個(gè)筆記,記錄并鞏固學(xué)到的知識(shí)。 JavaScript異步編程的兩個(gè)核心難點(diǎn) 異步I/O、事件驅(qū)動(dòng)使得...
摘要:異步問題回調(diào)地獄首先,我們來看下異步編程中最常見的一種問題,便是回調(diào)地獄。同時(shí)使用也是異步編程最基礎(chǔ)和核心的一種解決思路?;?,目前也被廣泛運(yùn)用,其是異步編程的一種解決方案,比傳統(tǒng)的回調(diào)函數(shù)解決方案更合理和強(qiáng)大。 關(guān)于 微信公眾號(hào):前端呼啦圈(Love-FED) 我的博客:勞卜的博客 知乎專欄:前端呼啦圈 前言 在實(shí)際編碼中,我們經(jīng)常會(huì)遇到Javascript代碼異步執(zhí)行的場(chǎng)景...
摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。怎么處理每個(gè)引擎都有一個(gè)基本組件,稱為調(diào)用棧。也就是說,如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開調(diào)用棧的。每個(gè)異步函數(shù)在被送入調(diào)用棧之前必須通過回調(diào)隊(duì)列。例如方法是在中傳遞的回調(diào)函數(shù)。 ? 翻譯:瘋狂的技術(shù)宅 原文:www.valentinog.com/blog/engine… 從Call Stack,Global Me...
摘要:最受歡迎的引擎是,在和中使用,用于,以及所使用的。單線程的我們說是單線程的,因?yàn)橛幸粋€(gè)調(diào)用棧處理我們的函數(shù)。也就是說,如果有其他函數(shù)等待執(zhí)行,函數(shù)是不能離開調(diào)用棧的。每個(gè)異步函數(shù)在被送入調(diào)用棧之前必須通過回調(diào)隊(duì)列。 翻譯:瘋狂的技術(shù)宅原文:https://www.valentinog.com/bl... 本文首發(fā)微信公眾號(hào):前端先鋒歡迎關(guān)注,每天都給你推送新鮮的前端技術(shù)文章 sh...
閱讀 1591·2021-09-26 09:46
閱讀 2678·2021-09-07 09:59
閱讀 2766·2021-09-07 09:59
閱讀 1889·2019-08-30 14:20
閱讀 943·2019-08-26 13:39
閱讀 3186·2019-08-26 12:24
閱讀 782·2019-08-26 11:55
閱讀 1227·2019-08-23 16:49