摘要:缺點(diǎn)無(wú)法取消當(dāng)處于狀態(tài)時(shí),無(wú)法得知目前進(jìn)展到哪一個(gè)階段錯(cuò)誤不能被生成器什么是函數(shù)是提供的一種異步編程解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同函數(shù)有多種理解角度。
JavaScript的執(zhí)行機(jī)制在上篇文章中進(jìn)行了深入的探討,那么既然是一門(mén)單線程語(yǔ)言,如何進(jìn)行良好體驗(yàn)的異步編程呢回調(diào)函數(shù)Callbacks
當(dāng)程序跑起來(lái)時(shí),一般情況下,應(yīng)用程序(application program)會(huì)時(shí)常通過(guò)API調(diào)用庫(kù)里所預(yù)先備好的函數(shù)。但是有些庫(kù)函數(shù)(library function)卻要求應(yīng)用先傳給它一個(gè)函數(shù),好在合適的時(shí)候調(diào)用,以完成目標(biāo)任務(wù)。這個(gè)被傳入的、后又被調(diào)用的函數(shù)就稱(chēng)為回調(diào)函數(shù)(callback function)。
什么是異步"調(diào)用"在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒(méi)有返回結(jié)果。換句話說(shuō),當(dāng)一個(gè)異步過(guò)程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果。而是在"調(diào)用"發(fā)出后,"被調(diào)用者"通過(guò)狀態(tài)、通知來(lái)通知調(diào)用者,或通過(guò)回調(diào)函數(shù)處理這個(gè)調(diào)用。異步調(diào)用發(fā)出后,不影響后面代碼的執(zhí)行。
簡(jiǎn)單說(shuō)就是一個(gè)任務(wù)分成兩段,先執(zhí)行第一段,然后轉(zhuǎn)而執(zhí)行其他任務(wù),等做好了準(zhǔn)備,再回過(guò)頭執(zhí)行第二段。
在異步執(zhí)行的模式下,每一個(gè)異步的任務(wù)都有其自己一個(gè)或著多個(gè)回調(diào)函數(shù),這樣當(dāng)前在執(zhí)行的異步任務(wù)執(zhí)行完之后,不會(huì)馬上執(zhí)行事件隊(duì)列中的下一項(xiàng)任務(wù),而是執(zhí)行它的回調(diào)函數(shù),而下一項(xiàng)任務(wù)也不會(huì)等當(dāng)前這個(gè)回調(diào)函數(shù)執(zhí)行完,因?yàn)樗膊荒艽_定當(dāng)前的回調(diào)合適執(zhí)行完畢,只要引它被觸發(fā)就會(huì)執(zhí)行,
異步最早的解決方案是回調(diào)函數(shù),如事件的回調(diào),setInterval/setTimeout中的回調(diào)。但是回調(diào)函數(shù)有一個(gè)很常見(jiàn)的問(wèn)題,就是回調(diào)地獄的問(wèn)題
下面這幾種都屬于回調(diào)
事件回調(diào)
Node API
setTimeout/setInterval中的回調(diào)函數(shù)
ajax 請(qǐng)求
異步回調(diào)嵌套會(huì)導(dǎo)致代碼難以維護(hù),并且不方便統(tǒng)一處理錯(cuò)誤,不能 try catch會(huì)陷入回調(diào)地獄
fs.readFile(A, "utf-8", function(err, data) { fs.readFile(B, "utf-8", function(err, data) { fs.readFile(C, "utf-8", function(err, data) { fs.readFile(D, "utf-8", function(err, data) { //.... }); }); }); }); ajax(url, () => { // 處理邏輯 ajax(url1, () => { // 處理邏輯 ajax(url2, () => { // 處理邏輯 }) }) })Promise解決地獄回調(diào)階段
Promise 一定程度上解決了回調(diào)地獄的問(wèn)題,Promise 最早由社區(qū)提出和實(shí)現(xiàn),ES6 將其寫(xiě)進(jìn)了語(yǔ)言標(biāo)準(zhǔn),統(tǒng)一了用法,原生提供了Promise對(duì)象。
Promise存在三個(gè)狀態(tài)(state)pending、fulfilled、rejected
pending(等待態(tài))為初始態(tài),并可以轉(zhuǎn)化為fulfilled(成功態(tài))和rejected(失敗態(tài))
成功時(shí),不可轉(zhuǎn)為其他狀態(tài),且必須有一個(gè)不可改變的值(value)
失敗時(shí),不可轉(zhuǎn)為其他狀態(tài),且必須有一個(gè)不可改變的原因(reason)
new Promise((resolve, reject)=>{resolve(value)}) resolve為成功,接收參數(shù)value,狀態(tài)改變?yōu)閒ulfilled,不可再次改變。
new Promise((resolve, reject)=>{reject(reason)}) reject為失敗,接收參數(shù)reason,狀態(tài)改變?yōu)閞ejected,不可再次改變。
若是executor函數(shù)報(bào)錯(cuò) 直接執(zhí)行reject();
Promise 是一個(gè)構(gòu)造函數(shù),new Promise 返回一個(gè) promise對(duì)象const promise = new Promise((resolve, reject) => { // 異步處理 // 處理結(jié)束后、調(diào)用resolve 或 reject });then方法注冊(cè) 當(dāng)resolve(成功)/reject(失敗)的回調(diào)函數(shù)
// onFulfilled 參數(shù)是用來(lái)接收promise成功的值, // onRejected 參數(shù)是用來(lái)接收promise失敗的原因 //兩個(gè)回調(diào)返回的都是promise,這樣就可以鏈?zhǔn)秸{(diào)用 promise.then(onFulfilled, onRejected);
const promise = new Promise((resolve, reject) => { resolve("fulfilled"); // 狀態(tài)由 pending => fulfilled }); promise.then(result => { // onFulfilled console.log(result); // "fulfilled" }, reason => { // onRejected 不會(huì)被調(diào)用 })then方法的鏈?zhǔn)秸{(diào)用
Promise對(duì)象的then方法返回一個(gè)新的Promise對(duì)象,因此可以通過(guò)鏈?zhǔn)秸{(diào)用then方法。then方法接收兩個(gè)函數(shù)作為參數(shù),第一個(gè)參數(shù)是Promise執(zhí)行成功時(shí)的回調(diào),第二個(gè)參數(shù)是Promise執(zhí)行失敗時(shí)的回調(diào)。兩個(gè)函數(shù)只會(huì)有一個(gè)被調(diào)用,函數(shù)的返回值將被用作創(chuàng)建then返回的Promise對(duì)象。這兩個(gè)參數(shù)的返回值可以是以下三種情況中的一種:
return 一個(gè)同步的值 ,或者 undefined(當(dāng)沒(méi)有返回一個(gè)有效值時(shí),默認(rèn)返回undefined),then方法將返回一個(gè)resolved狀態(tài)的Promise對(duì)象,Promise對(duì)象的值就是這個(gè)返回值。
return 另一個(gè) Promise,then方法將根據(jù)這個(gè)Promise的狀態(tài)和值創(chuàng)建一個(gè)新的Promise對(duì)象返回。
throw 一個(gè)同步異常,then方法將返回一個(gè)rejected狀態(tài)的Promise, 值是該異常。
解決層層回調(diào)問(wèn)題//對(duì)應(yīng)上面第一個(gè)node讀取文件的例子 function read(url) { return new Promise((resolve, reject) => { fs.readFile(url, "utf8", (err, data) => { if(err) reject(err); resolve(data); }); }); } read(A).then(data => { return read(B); }).then(data => { return read(C); }).then(data => { return read(D); }).catch(reason => { console.log(reason); });
//對(duì)應(yīng)第二個(gè)ajax請(qǐng)求例子 ajax(url) .then(res => { console.log(res) return ajax(url1) }).then(res => { console.log(res) return ajax(url2) }).then(res => console.log(res))
可以看到,Promise在一定程度上其實(shí)改善了回調(diào)函數(shù)的書(shū)寫(xiě)方式,最明顯的一點(diǎn)就是去除了橫向擴(kuò)展,無(wú)論有再多的業(yè)務(wù)依賴,通過(guò)多個(gè)then(...)來(lái)獲取數(shù)據(jù),讓代碼只在縱向進(jìn)行擴(kuò)展;另外一點(diǎn)就是邏輯性更明顯了,將異步業(yè)務(wù)提取成單個(gè)函數(shù),整個(gè)流程可以看到是一步步向下執(zhí)行的,依賴層級(jí)也很清晰,最后需要的數(shù)據(jù)是在整個(gè)代碼的最后一步獲得。
所以,Promise在一定程度上解決了回調(diào)函數(shù)的書(shū)寫(xiě)結(jié)構(gòu)問(wèn)題,但回調(diào)函數(shù)依然在主流程上存在,只不過(guò)都放到了then(...)里面,和我們大腦順序線性的思維邏輯還是有出入的。
無(wú)法取消 Promise
當(dāng)處于pending狀態(tài)時(shí),無(wú)法得知目前進(jìn)展到哪一個(gè)階段
錯(cuò)誤不能被 try catch
生成器Generators/ yield 什么是GeneratorGenerator 函數(shù)是 ES6 提供的一種異步編程解決方案,語(yǔ)法行為與傳統(tǒng)函數(shù)完全不同
Generator 函數(shù)有多種理解角度。語(yǔ)法上,首先可以把它理解成,Generator 函數(shù)是一個(gè)狀態(tài)機(jī),封裝了多個(gè)內(nèi)部狀態(tài)。
執(zhí)行 Generator 函數(shù)會(huì)返回一個(gè)遍歷器對(duì)象,也就是說(shuō),Generator 函數(shù)除了狀態(tài)機(jī),還是一個(gè)遍歷器對(duì)象生成函數(shù)。返回的遍歷器對(duì)象,可以依次遍歷 Generator 函數(shù)內(nèi)部的每一個(gè)狀態(tài)。形式上,Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。
一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);
二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)
Generator調(diào)用方式Generator 函數(shù)的調(diào)用方法與普通函數(shù)一樣,也是在函數(shù)名后面加上一對(duì)圓括號(hào)。不同的是,調(diào)用 Generator 函數(shù)后,該函數(shù)并不執(zhí)行,返回的也不是函數(shù)運(yùn)行結(jié)果,而是一個(gè)指向內(nèi)部狀態(tài)的指針對(duì)象,也就是上一章介紹的遍歷器對(duì)象(Iterator Object)。
下一步,必須調(diào)用遍歷器對(duì)象的next方法,使得指針移向下一個(gè)狀態(tài)。也就是說(shuō),每次調(diào)用next方法,內(nèi)部指針就從函數(shù)頭部或上一次停下來(lái)的地方開(kāi)始執(zhí)行,直到遇到下一個(gè)yield表達(dá)式(或return語(yǔ)句)為止。換言之,Generator 函數(shù)是分段執(zhí)行的,yield表達(dá)式是暫停執(zhí)行的標(biāo)記,而next方法可以恢復(fù)執(zhí)行。
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 }了解Co
可以看到上個(gè)例子當(dāng)中我們需要一步一步去調(diào)用next這樣也會(huì)很麻煩,這時(shí)我們可以引入co來(lái)幫我們控制
Co是一個(gè)為Node.js和瀏覽器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加優(yōu)雅的方式編寫(xiě)非阻塞代碼。
Co 函數(shù)庫(kù)約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise 對(duì)象,而 async 函數(shù)的 await 命令后面,可以跟 Promise 對(duì)象和原始類(lèi)型的值(數(shù)值、字符串和布爾值,但這時(shí)等同于同步操作)。
說(shuō)白了就是幫你自動(dòng)執(zhí)行你的Generator不用手動(dòng)調(diào)用next
我們可以通過(guò) Generator 函數(shù)解決回調(diào)地獄的問(wèn)題,可以把之前的回調(diào)地獄例子改寫(xiě)為如下代碼:
const co = require("co"); co( function* read() { yield readFile(A, "utf-8"); yield readFile(B, "utf-8"); yield readFile(C, "utf-8"); //.... } ).then(data => { //code }).catch(err => { //code });
function *fetch() { yield ajax(url, () => {}) yield ajax(url1, () => {}) yield ajax(url2, () => {}) } let it = fetch() let result1 = it.next() let result2 = it.next() let result3 = it.next()終極解決方案Async/ await
async 函數(shù)是Generator 函數(shù)的語(yǔ)法糖,是對(duì)Generator做了進(jìn)一步的封裝。Async特點(diǎn)
當(dāng)調(diào)用一個(gè) async 函數(shù)時(shí),會(huì)返回一個(gè) Promise 對(duì)象。
async function async1() { return "1" } console.log(async1()) // -> Promise {: "1"}
當(dāng)這個(gè) async 函數(shù)返回一個(gè)值時(shí),Promise 的 resolve 方法會(huì)負(fù)責(zé)傳遞這個(gè)值;
當(dāng) async 函數(shù)拋出異常時(shí),Promise 的 reject 方法也會(huì)傳遞這個(gè)異常值。
async 函數(shù)中可能會(huì)有 await 表達(dá)式,這會(huì)使 async 函數(shù)暫停執(zhí)行,等待 Promise 的結(jié)果出來(lái),然后恢復(fù)async函數(shù)的執(zhí)行并返回解析(resolved)。
內(nèi)置執(zhí)行器。 Generator 函數(shù)的執(zhí)行必須靠執(zhí)行器,所以才有了 co 函數(shù)庫(kù),而 async 函數(shù)自帶執(zhí)行器。也就是說(shuō),async 函數(shù)的執(zhí)行,與普通函數(shù)一模一樣,只要一行。
更廣的適用性。co 模塊約定,yield 命令后面只能是 Thunk 函數(shù)或 Promise對(duì)象。而 async 函數(shù)的 await 命令后面則可以是 Promise 或者 原始類(lèi)型的值(Number,string,boolean,但這時(shí)等同于同步操作)
await特點(diǎn)await 操作符用于等待一個(gè)Promise 對(duì)象。它只能在異步函數(shù) async function 中使用。
[return_value] = await expression;
await 表達(dá)式會(huì)暫停當(dāng)前 async function 的執(zhí)行,等待 Promise 處理完成。若 Promise 正常處理(fulfilled),其回調(diào)的resolve函數(shù)參數(shù)作為 await 表達(dá)式的值,繼續(xù)執(zhí)行 async function。
若 Promise 處理異常(rejected),await 表達(dá)式會(huì)把 Promise 的異常原因拋出。
另外,如果 await 操作符后的表達(dá)式的值不是一個(gè) Promise,則返回該值本身。
重點(diǎn):遇到 await 表達(dá)式時(shí),會(huì)讓 async 函數(shù) 暫停執(zhí)行,等到 await 后面的語(yǔ)句(Promise)狀態(tài)發(fā)生改變(resolved或者rejected)之后,再恢復(fù) async 函數(shù)的執(zhí)行(再之后 await 下面的語(yǔ)句),并返回解析值(Promise的值)
為什么await可以暫停執(zhí)行并等到Promise的狀態(tài)改變?cè)倩謴?fù)執(zhí)行呢promise就是做這件事的 , 它會(huì)自動(dòng)等到Promise決議以后的返回值,resolve(...)或者reject(...)都可以。
async內(nèi)部會(huì)在promise.then(callback),回調(diào)函數(shù)里調(diào)用 next()... (還有用Thunk的, 也是為了做這個(gè)事的);
簡(jiǎn)單說(shuō) , async/awit 就是對(duì)上面gennerator自動(dòng)化流程的封裝 , 讓每一個(gè)異步任務(wù)都是自動(dòng)化的執(zhí)行 , 當(dāng)?shù)谝粋€(gè)異步任務(wù)readFile(A)執(zhí)行完如上一點(diǎn)說(shuō)明的, async內(nèi)部自己執(zhí)行next(),調(diào)用第二個(gè)任務(wù)readFile(B);
這里引入ES6阮一峰老師的例子 const fs = require("fs"); const readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) return reject(error); resolve(data); }); }); }; async function read() { await readFile(A);//執(zhí)行到這里停止往下執(zhí)行,等待readFile內(nèi)部resolve(data)后,再往下執(zhí)行 await readFile(B); await readFile(C); //code } //這里可用于捕獲錯(cuò)誤 read().then((data) => { //code }).catch(err => { //code });參考文章
http://es6.ruanyifeng.com/
https://juejin.im/post/5aa786...
https://juejin.im/post/5b83cb...
https://juejin.im/post/596e14...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106061.html
摘要:的翻譯文檔由的維護(hù)很多人說(shuō),阮老師已經(jīng)有一本關(guān)于的書(shū)了入門(mén),覺(jué)得看看這本書(shū)就足夠了。前端的異步解決方案之和異步編程模式在前端開(kāi)發(fā)過(guò)程中,顯得越來(lái)越重要。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。 JavaScript Promise 迷你書(shū)(中文版) 超詳細(xì)介紹promise的gitbook,看完再不會(huì)promise...... 本書(shū)的目的是以目前還在制定中的ECMASc...
摘要:從最開(kāi)始的到封裝后的都在試圖解決異步編程過(guò)程中的問(wèn)題。為了讓編程更美好,我們就需要引入來(lái)降低異步編程的復(fù)雜性。寫(xiě)一個(gè)符合規(guī)范并可配合使用的寫(xiě)一個(gè)符合規(guī)范并可配合使用的理解的工作原理采用回調(diào)函數(shù)來(lái)處理異步編程。 JavaScript怎么使用循環(huán)代替(異步)遞歸 問(wèn)題描述 在開(kāi)發(fā)過(guò)程中,遇到一個(gè)需求:在系統(tǒng)初始化時(shí)通過(guò)http獲取一個(gè)第三方服務(wù)器端的列表,第三方服務(wù)器提供了一個(gè)接口,可通過(guò)...
摘要:模塊化是隨著前端技術(shù)的發(fā)展,前端代碼爆炸式增長(zhǎng)后,工程化所采取的必然措施。目前模塊化的思想分為和。特別指出,事件不等同于異步,回調(diào)也不等同于異步。將會(huì)討論安全的類(lèi)型檢測(cè)惰性載入函數(shù)凍結(jié)對(duì)象定時(shí)器等話題。 Vue.js 前后端同構(gòu)方案之準(zhǔn)備篇——代碼優(yōu)化 目前 Vue.js 的火爆不亞于當(dāng)初的 React,本人對(duì)寫(xiě)代碼有潔癖,代碼也是藝術(shù)。此篇是準(zhǔn)備篇,工欲善其事,必先利其器。我們先在代...
摘要:本文最早為雙十一而作,原標(biāo)題雙大前端工程師讀書(shū)清單,以付費(fèi)的形式發(fā)布在上。發(fā)布完本次預(yù)告后,捕捉到了一個(gè)友善的吐槽讀書(shū)清單也要收費(fèi)。這本書(shū)便從的異步編程講起,幫助我們?cè)O(shè)計(jì)快速響應(yīng)的網(wǎng)絡(luò)應(yīng)用,而非簡(jiǎn)單的頁(yè)面。 本文最早為雙十一而作,原標(biāo)題雙 11 大前端工程師讀書(shū)清單,以付費(fèi)的形式發(fā)布在 GitChat 上。發(fā)布之后在讀者圈群聊中和讀者進(jìn)行了深入的交流,現(xiàn)免費(fèi)分享到這里,不足之處歡迎指教...
摘要:本文最早為雙十一而作,原標(biāo)題雙大前端工程師讀書(shū)清單,以付費(fèi)的形式發(fā)布在上。發(fā)布完本次預(yù)告后,捕捉到了一個(gè)友善的吐槽讀書(shū)清單也要收費(fèi)。這本書(shū)便從的異步編程講起,幫助我們?cè)O(shè)計(jì)快速響應(yīng)的網(wǎng)絡(luò)應(yīng)用,而非簡(jiǎn)單的頁(yè)面。 本文最早為雙十一而作,原標(biāo)題雙 11 大前端工程師讀書(shū)清單,以付費(fèi)的形式發(fā)布在 GitChat 上。發(fā)布之后在讀者圈群聊中和讀者進(jìn)行了深入的交流,現(xiàn)免費(fèi)分享到這里,不足之處歡迎指教...
閱讀 2625·2023-04-25 20:50
閱讀 4019·2023-04-25 18:45
閱讀 2253·2021-11-17 17:00
閱讀 3362·2021-10-08 10:05
閱讀 3108·2019-08-30 15:55
閱讀 3531·2019-08-30 15:44
閱讀 2381·2019-08-29 13:51
閱讀 1142·2019-08-29 12:47