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