精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

JavaScript異步之從回調函數到Promise

原創
開發 前端
JavaScript的異步處理是前端工程師必須接觸的一塊內容。ES6在JavaScript異步的處理上引入了新的特性,使得程序員能夠更加優雅地處理異步問題。

[[250903]]

【51CTO.com原創稿件】 JavaScript的異步處理是前端工程師必須接觸的一塊內容。ES6在JavaScript異步的處理上引入了新的特性,使得程序員能夠更加優雅地處理異步問題。

若您想通過本教程直接上手Promise,那么請按順序閱讀。

若您只是想了解Promise概念,那么請直接閱讀每章的***小節,等需要的時候,再回過頭來看具體的例子,從而不至于浪費您太多時間。

1.基于回調函數

1.1.異步動作與回調函數

在JavaScript中往往需要處理很多異步動作(asynchronous actions),如后臺請求某個dashboard上的顯示數據、響應一條定時信息。異步動作的執行不會阻塞其他動作,且在執行完成之后,由回調函數(callback)處理異步動作的結果。

假如你想載入一個JavaScript 腳本,并在腳本載入完畢之后調用一個回調函數來完成載入之后的操作。代碼片1.1-1實現了這樣一個異步函數loadScript。

代碼片1.1-1 

  1. //src代表JavaScript 腳本的URL,callback代表自定義回調函數 
  2. function loadScript(src, callback) { 
  3.   let script = document.createElement('script'); 
  4.   script.src = src;  
  5.   script.onload = () => callback(script);  
  6.   document.head.append(script); 
  7.  
  8.  loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => { 
  9.   alert(`Cool, the ${script.src} is loaded`); 
  10.   alert( _ ); // function declared in the loaded script 
  11. });  

***一行loadScript的調用可以讀作:異步函數loadScript載入腳本,并在載入執行完畢后調用毀掉函數彈出提示框。

1.2.異步動作的順序執行

很多場景下,往往需要依次執行多個異步動作(上一個異步動作結束之后才能執行下一個)。通過結合回調函數,可以寫成一個“嵌套”的異步函數,如下1.2-1。

代碼片1.2-1 

  1. loadScript('/my/script.js'function(script) {  
  2.   loadScript('/my/script2.js'function(script) {  
  3.     loadScript('/my/script3.js'function(script) { 
  4.       // ...continue after all scripts are loaded 
  5.     });  
  6.   })  
  7. });  

1.3.異步動作的異常處理

在實際場景中,還需要根據異步函數的執行狀態(正?;蛘弋惓?來執行不同的回調函數。代碼片1.3-1是代碼片1.1-1中的改進版本,通過增加對onerror事件的響應,異步動作拋出的異常能由用戶提供的函數來接管。此時要注意的是,這里的回調函數與前面不同,它是形式為function(error,script)的函數。

代碼片1.3-1 

  1. function loadScript(src, callback) { 
  2.   let script = document.createElement('script'); 
  3.   script.src = src; 
  4.  
  5.   script.onload = () => callback(, script); // 1 
  6.   script.onerror = () => callback(new Error(`Script load error for ${src}`)); // 2 
  7.  
  8.   document.head.append(script); 
  9.  
  10. loadScript('/my/script.js'function(error, script) { 
  11.   if (error) { 
  12.     // handle error 
  13.   } else { 
  14.     // script loaded successfully 
  15.   } 
  16. });  

這里loadScript的調用可以讀作:異步函數loadScript載入腳本,并在腳本載入執行失敗后調用2,在腳本載入執行成功后調用1。

1.4. 異步動作帶來的問題——惡魔金字塔

結合1.2和1.3,可以得到一個包含異常處理和多個異步動作順序執行的例子,如代碼片1.4-1所示。

代碼片1.4-1 

  1. loadScript('1.js'function(error, script) {  
  2.   if (error) { 
  3.     handleError(error); 
  4.   } else { 
  5.     // ... 
  6.     loadScript('2.js'function(error, script) { 
  7.       if (error) { 
  8.         handleError(error); 
  9.       } else { 
  10.         // ... 
  11.         loadScript('3.js'function(error, script) { 
  12.           if (error) { 
  13.             handleError(error); 
  14.           } else { 
  15.             // ...continue after all scripts are loaded (*) 
  16.           } 
  17.         }); 
  18.  
  19.       } 
  20.     }) 
  21.   } 
  22. });  

可以看到,隨著嵌套的深入,從左往右看代碼就形成了一個金字塔結構的嵌套。這樣得到的代碼非常不利于維護和拓展,因此也被稱為惡魔金字塔(Pyramid of doom)。

代碼片1.4-2解決了惡魔金字塔的問題,但也引入了可讀性和命名空間的問題,因此不算一個優雅的解決方案。

代碼片1.4-2 

  1. loadScript('1.js', step1);  
  2. function step1(error, script) { 
  3.   if (error) { 
  4.     handleError(error); 
  5.   } else { 
  6.     // ... 
  7.     loadScript('2.js', step2); 
  8.   } 
  9.  
  10. function step2(error, script) { 
  11.   if (error) { 
  12.     handleError(error); 
  13.   } else { 
  14.     // ... 
  15.     loadScript('3.js', step3); 
  16.   } 
  17.  
  18. function step3(error, script) { 
  19.   if (error) { 
  20.     handleError(error); 
  21.   } else { 
  22.     // ...continue after all scripts are loaded (*) 
  23.   } 
  24. };  

2.基于Promise

2.1.Promise是什么

Promise是為了解決回調函數的一些缺陷而在ES6中定義的異步解決方案,它的訂閱模式與鏈式表達式能讓開發者更加方便的定義自己的異步動作。

為了更好的理解Promise想要解決的問題,可以想象這樣一個場景:想象你是一個知名歌手,你的粉絲問你單曲發售的消息。你讓他們訂閱你的消息,這樣在你準備好專輯之后,就有專人負責通知你的粉絲,讓他們獲取關于單曲的信息,好讓他們購買專輯并推薦給身邊的朋友。

這里”歌手發布一首單曲”就是一個異步動作的生產代碼(producing code)(實際中可能是向服務器請求一條數據),“粉絲接受單曲發售的通知,然后購買專輯并推薦給身邊的朋友”,這一動作就是消費代碼(consuming code)(類似回調函數),而連接兩者的“專人”就是Promise。

Promise是一個JavaScript對象,它將生產代碼和消費代碼聯系起來,從而在生產代碼完成異步動作后,訂閱異步動作的消費代碼就能獲取結果(假如初次接觸Promise,到這至少已經理解一半了。但想了解如何使用Promise或者想閱讀Promise相關的代碼,你還得繼續)。

2.2.生成一個Promise對象

根據2.1可知,Promise起到的就是“橋接”生產代碼和消費代碼的作用。Promise對象通過傳入一個執行器(executor)執行生產代碼,消費代碼通過.then和.catch方法訂閱結果(生產代碼的結果可能是正常的返回值也可能是一個異常)。理解了生產代碼的傳入和消費代碼如何訂閱結果,也就明白了Promise的用法。

2.2.1.生產代碼

Promise對象通過傳入一個執行器(executor)執行生產代碼。執行器是形式為function(resolve, reject)的函數,它包含了異步動作的生產代碼。執行器會在Promise對象創建的時候自動執行。當執行器執行完成任務之后,會調用resolve(解析)來接受異步動作正常執行完畢的結果,調用reject(拒絕)來接受一個在異步動作中拋出的異常(Error)。

這樣可能還是不夠直觀,那就看看代碼片2.2.1-1,它利用Promise改造了代碼片1.3-1。onload(表示腳本正常載入完畢)和onerror(載入過程中拋出異常)兩個異步狀態分別執行了resolve和reject方法,分別接受一個DOM對象和Error對象。若生產代碼調用resolve解析,則Promise會把DOM對象作為結果通知給消費代碼;反之若調用reject方法,則Promise把Error對象作為結果通知給消費代碼。

代碼片2.2.1-1 

  1. function loadScript(src) { 
  2.   return new Promise(function(resolve, reject) { 
  3.     let script = document.createElement('script'); 
  4.     script.src = src; 
  5.  
  6.     script.onload = () => resolve(script); 
  7.     script.onerror = () => reject(new Error("Script load error: " + src)); 
  8.  
  9.     document.head.append(script); 
  10.   }); 
  11.  

Promise如何能夠得知一個異步狀態?這是因為Promise對象維護了兩個重要內部屬性:

  • state(狀態) :初始是“pending”,執行完畢之后變化成“fulfilled”或者“rejected”。
  • result(結果):異步動作的結果值??梢匀我庵付?,默認是undefined。

當調用resolve時設置state為fulfilled,并把result作為參數傳給resolve;當調用reject時設置state為rejected,并把result作為參數傳給rejected。從邏輯上來看,rejected和resolve可以看做是異步動作結果的”容器”,一旦state改變,Promise就從“容器”中取出result并通知消費代碼處理。

2.2.2.消費代碼

.then和.catch方法可以使消費代碼能夠接受Promise對象發送的消息,訂閱生產代碼的結果。

2.2.2.1..then方法

.then方法的強大之處在于它的靈活性,可以定義兩個函數接受分別接受resolve和reject返回的結果。代碼片2.2.2.1-1和代碼片2.2.2.1-2分別反映了.then方法對resolve和reject結果的不同的響應。

代碼片2.2.2.1-1 

  1. let promise = new Promise(function(resolve, reject) { 
  2.   setTimeout(() => resolve("done!"), 1000); 
  3. }); 
  4.  
  5. // resolve runs the first function in .then 
  6. promise.then
  7.   result => alert(result), // shows "done!" after 1 second 
  8.   error => alert(error) // doesn't run 
  9. );  

代碼片2.2.2.1-2 

  1. let promise = new Promise(function(resolve, reject) { 
  2.   setTimeout(() => reject(new Error("Whoops!")), 1000); 
  3. }); 
  4.  
  5. // reject runs the second function in .then 
  6. promise.then
  7.   result => alert(result), // doesn't run 
  8.   error => alert(error) // shows "Error: Whoops!" after 1 second 
  9. );  

若.then方法只傳入了一個參數,那么默認消費代碼只訂閱resovle接受的結果,如代碼片2.2.2.1-3所示。

代碼片2.2.2.1-3 

  1. let promise = new Promise(resolve => { 
  2.   setTimeout(() => resolve("done!"), 1000); 
  3. }); 
  4.  
  5. promise.then(alert); // shows "done!" after 1 second  

2.2.2.2..catch方法

若消費代碼想單獨捕獲異常(訂閱異常結果),可以考慮使用.catch。.catch是.then(null,alert)的一個快捷方式。代碼片2.2.2.2-1是這兩種的實現方式的例子。

代碼片2.2.2.2-1 

  1. let promise = new Promise((resolve, reject) => { 
  2.   setTimeout(() => reject(new Error("Whoops!")), 1000); 
  3. }); 
  4.  
  5. // .catch(f) is the same as promise.then(null, f) 
  6. promise.catch(alert); // shows "Error: Whoops!" after 1 second 
  7.  
  8. // .catch(f) is the same as promise.then(null, f) 
  9. promise.then(,alert); // shows "Error: Whoops!" after 1 second  

2.3.使用Promise需要注意的一些細節

2.3.1.一個執行器只會執行一次resolve或者reject

在代碼片2.3.1-1的執行器中,除了***個resolve之外的其他resolve或者reject都會被忽略。這兩個方法中的額外參數也會被忽略。

代碼片2.3.1-1 

  1. let promise = new Promise(function(resolve, reject) { 
  2.   resolve("done"); 
  3.  
  4.   reject(new Error("…")); // ignored 
  5.   setTimeout(() => resolve("…")); // ignored 
  6. });  

2.3.2.使用Error對象或者繼承自Error類的對象作為reject的參數

這是一個好的實踐,這樣能對異常進行更好的處理(比如針對不通的異常類型進行不同的操作)。

2.3.3.立即執行resolve/reject

雖然在實際中,執行器往往執行一些異步操作,但是你也可以在執行器中立刻執行resolve或者reject方法,這完全沒有關系。這樣你的結果會被直接投遞到消費代碼。如代碼片2.4.3-1所示。

代碼片2.3.3-1 

  1. let promise = new Promise(function(resolve, reject) { 
  2.   // not taking our time to do the job 
  3.   resolve(123); // immediately give the result: 123 
  4. });  

2.3.4..then和.catch中定義的handler都是異步的

.then和.catch中定義的handler都是異步的,這意味著即使Promise立刻執行了到了resolve或者reject,handler也必須等待當前的代碼執行完畢才能被加載,如代碼片2.3.4-1所示。雖然執行器立即執行了resolve得到了結果,但是.then(alert)也在***被調用。如代碼片2.3.4-1所示。

代碼片2.3.4-1 

  1. // an "immediately" resolved Promise 
  2. const executor = resolve => resolve("done!"); 
  3. const promise = new Promise(executor);  
  4. promise.then(alert); // this alert shows last (*)  
  5. alert("code finished"); // this alert shows first  

3.Promise鏈

在實際中,很多時候往往需要順序執行異步任務,但是用也帶來了”惡魔金字塔”的問題(如1.4節描述)。引入Promise鏈,我們可以優雅的解決這個問題。

3.1.Promise鏈中的.then

多個.then方法可以構成一條Promise鏈。代碼片3.1.-1就是一個簡單的例子。

代碼片3.1.-1 

  1. new Promise(function(resolve, reject) {  
  2.   setTimeout(() => resolve(1), 1000); // (*)  
  3. }).then(function(result) { // (**)  
  4.   alert(result); // 1 
  5.   return result * 2;  
  6. }).then(function(result) { // (***)  
  7.   alert(result); // 2 
  8.   return result * 2; 
  9.  
  10. }).then(function(result) {  
  11.   alert(result); // 4 
  12.   return result * 2;  
  13. });  

執行該代碼,結果為1——2——4,這是因為.then返回一個Promise方法,并隱式地把值賦給了Promise對象的result屬性,使得***個Promise的result屬性能夠通過調用鏈不斷傳遞。

倘若想在.then中包含異步操作,則必須返回一個包含異步對象的Promise。在處理異步操作期間,Promise鏈上的handler均不會執行,待異步操作完成,才將結果傳遞到鏈的下一個節點。

代碼片3.1.-2 

  1. new Promise(function(resolve, reject) {  
  2.   setTimeout(() => resolve(1), 1000);  
  3. }).then(function(result) {  
  4.   alert(result); // 1  
  5.   return new Promise((resolve, reject) => { // (*) 
  6.     setTimeout(() => resolve(result * 2), 1000); 
  7.   }); 
  8.  
  9. }).then(function(result) { // (**)  
  10.   alert(result); // 2  
  11.   return new Promise((resolve, reject) => { 
  12.     setTimeout(() => resolve(result * 2), 1000); 
  13.   }); 
  14.  
  15. }).then(function(result) {  
  16.   alert(result); // 4  
  17. });  

在代碼片3.1.-2中,***的結果也是1——2——4,但是每個alter都相隔1s才會顯示??梢岳斫鉃閞eturn一個Promise阻礙了結果的傳播,必須要等這個異步動作結束,結果才能在Promise鏈中繼續傳遞。

3.2.Promise鏈中的.catch

.catch可以對Promise鏈中的異常進行處理??紤]代碼片3.2.-1。假設我們引入fetch函數(用來獲取json)獲取用戶的頭像(avatar)并顯示,.catch可以捕獲該Promise鏈中拋出的異常。

代碼片3.2.-1 

  1. fetch('/article/promise-chaining/user.json'
  2.   .then(response => response.json()) 
  3.   .then(user => fetch(`https://api.github.com/users/${user.name}`)) 
  4.   .then(response => response.json()) 
  5.   .then(githubUser => new Promise(function(resolve, reject) { 
  6.     let img = document.createElement('img'); 
  7.     img.src = githubUser.avatar_url; 
  8.     img.className = "promise-avatar-example"
  9.     document.body.append(img); 
  10.  
  11.     setTimeout(() => { 
  12.       img.remove(); 
  13.       resolve(githubUser); 
  14.     }, 3000); 
  15.   })) 
  16.   .catch(error => alert(error.message));  

但是這樣還不夠好,在實際編碼中,常常需要在代碼中拋出異常,并根據異常的類型來做相應的處理。幸運的是,Promise鏈默認把在處理鏈中拋出的異常當reject進行處理,并讓用戶用.catch捕獲。如代碼片3.2.-2所示,loadJson函數在Promise鏈中會檢測HTTP的狀態碼,若不為200(不成功),就拋出自定義異常“new HttpError(response)”,并被catch所捕獲。

代碼片3.2.-2 

  1. class HttpError extends Error { // (1) 
  2.   constructor(response) { 
  3.     super(`${response.status} for ${response.url}`); 
  4.     this.name = 'HttpError'
  5.     this.response = response; 
  6.   } 
  7.  
  8. function loadJson(url) { // (2) 
  9.   return fetch(url) 
  10.     .then(response => { 
  11.       if (response.status == 200) { 
  12.         return response.json(); 
  13.       } else { 
  14.         throw new HttpError(response); 
  15.       } 
  16.     }) 
  17.  
  18. loadJson('no-such-user.json') // (3) 
  19.   .catch(alert); // HttpError: 404 for .../no-such-user.json  

3.3.重新拋出異常及未處理異常

在一般的try…catch…結構中,若一個異常無法處理,往往可以重新拋出(Rethrowing)給上一級的異常處理函數處理。Promise鏈也支持這種形式。在Promis中也可以重新拋出異常,并被最近一個.catch所捕獲。

考慮代碼片3.3.-1。在Promise對象拋出一個異常”Whoops!”之后,這個Promise對象的狀態變為拒絕(reject),鏈上最近的一個.catch方法被調用,并判斷是否是URI異常,顯然”Whoops!”不屬于這類異常,因此顯示”Can’t handle such error”,并重新拋出異常。該異常被鏈上的第二個.catch所捕獲,最終顯示”The unknown error has occurred: Error: Whoops!”。

代碼片3.3.-1 

  1. // the execution: catch -> catch -> then 
  2. new Promise(function(resolve, reject) { 
  3.  
  4.   throw new Error("Whoops!"); 
  5.  
  6. }).catch(function(error) { // (*) 
  7.  
  8.   if (error instanceof URIError) { 
  9.     // handle it 
  10.   } else { 
  11.     alert("Can't handle such error"); 
  12.  
  13.     throw error; // throwing this or another error jumps to the next catch 
  14.   } 
  15.  
  16. }).then(function() { 
  17.   /* never runs here */ 
  18. }).catch(error => { // (**) 
  19.  
  20.   alert(`The unknown error has occurred: ${error}`); 
  21.   // don't return anything => execution goes the normal way 
  22.  
  23. });  

一般來說Promise鏈底部寫上.catch來捕獲異常是一個非常好的習慣。假如不這樣做,那么javaScript引擎會捕獲該異常并在控制臺顯示 。當然,也可以在瀏覽器中可以通過注冊一個unhandledrejection事件(unhandledrejection事件是HTML標準的一部分)監聽器,來捕獲未處理異常,如代碼片3.3.-2所示:

代碼片3.3.-2 

  1. window.addEventListener('unhandledrejection'function(event) { 
  2.   // the event object has two special properties: 
  3.   alert(event.promise); // [object Promise] - the promise that generated the error 
  4.   alert(event.reason); // Error: Whoops! - the unhandled error object 
  5. }); 
  6.  
  7. new Promise(function() { 
  8.   throw new Error("Whoops!"); 
  9. }); // no catch to handle the er  

4.Promise API

Promise對象有四個靜態方法:resolve/reject/all/race,可以在某些場景下讓處理Promise對象的代碼變得更加簡潔。

4.1.Promise.resolve/Promise.reject

Promise.resolve/Promise.reject直接返回一個已經被resolve/reject的Promise對象。代碼片4.1-1和代碼片4.1-2分別顯示了Promise.resolve和Promise.reject的等價形式。值得注意的是,Promise.resolve/Promise.reject返回的是Promise對象,因此也可用.then/.catch構成Promise鏈。

代碼片4.1-1 

  1. let promise = Promise.resolve(value); 
  2. let promise = new Promise(resolve => resolve(value)); 
  3. 代碼片4.1-2  

代碼片4.1-2 

  1. let promise = Promise.reject(error);  
  2. let promise = new Promise((resolve, reject) => reject(error));  

4.2.Promise.all

Promise.all接受一個可迭代對象(往往是Promise數組)作為輸入,并行地執行它們,等待所有Promise執行完畢之后返回一個Promise對象。這個Promise對象的result屬性是包含所有對應結果的一個數組,如代碼片4.2-1所示。

代碼片4.2-1 

  1. Promise.all([ 
  2.   new Promise((resolve, reject) => setTimeout(() => resolve(1), 3000)), // 1 
  3.   new Promise((resolve, reject) => setTimeout(() => resolve(2), 2000)), // 2 
  4.   new Promise((resolve, reject) => setTimeout(() => resolve(3), 1000))  // 3 
  5. ]).then(alert); // 1,2,3 when promises are ready: each promise contributes an array member  

需要指出的是,當傳入的可迭代對象中包含非Promise對象的元素時,Promise.all會自動調用Promise.resolve方法將其包裝成一個Promise對象并返回。如代碼片4.2-2所示。

代碼片4.2-2 

  1. Promise.all([ 
  2.   new Promise((resolve, reject) => { 
  3.     setTimeout(() => resolve(1), 1000) 
  4.   }), 
  5.   2, // treated as Promise.resolve(2) 
  6.   3  // treated as Promise.resolve(3) 
  7. ]).then(alert); // 1, 2, 3  

4.3.Promise.race

Promise.race接受一個可迭代對象(往往是Promise數組)作為輸入,并行地執行它們,將***個返回的Promise對象作為結果,如代碼片4.3-1所示。***alter的結果是1

代碼片4.3-1 

  1. Promise.race([ 
  2.   new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), 
  3.   new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)), 
  4.   new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) 
  5. ]).then(alert); // 1  

5.async/await

假如你是按順序讀完,那么到這里理解async/await關鍵字就非常容易。async/await關鍵字作為語法糖,能讓操作Promise的代碼更加簡潔可讀。

5.1.async

async關鍵詞置于你想修飾的函數前,可以將一個非Promise的結果通過Promise.resolve的封裝變成一個Promise對象,如代碼片5.1-1所示。

代碼片5.1-1 

  1. async function f() { 
  2.   return 1; 
  3.  
  4. f().then(alert); // 1 
  5. 5.2.await  

5.2.await

await的作用和.then非常相似,用來等待一個Promise對象的異步返回。await和async密不可分,await必須在async修飾的函數中才能使用。如代碼片5.2-1所示。

代碼片5.2-1 

  1. async function f() { 
  2.  
  3.   let promise = new Promise((resolve, reject) => { 
  4.     setTimeout(() => resolve("done!"), 1000) 
  5.   }); 
  6.  
  7.   let result = await promise; // wait till the promise resolves (*) 
  8.  
  9.   alert(result); // "done!" 
  10.  
  11. f();  

值得注意的是,一旦使用await,就可以使用try…catch來捕獲異常。相比.catch來說,這樣捕獲異常更加方便。

代碼片5.2-2 

  1. async function f() { 
  2.  
  3.   try { 
  4.     let response = await fetch('http://no-such-url'); 
  5.   } catch(err) { 
  6.     alert(err); // TypeError: failed to fetch 
  7.   } 
  8.  
  9. f();  

6.總結

本文參考在線教程并根據個人的實踐經驗有側重的總結了一下ES6的異步特性:Promise概念、基本用法、靜態方法以及兩個關鍵字async和await。這里沒有提到的是,Promise仍然有著一些缺點,比如它無法像RxJS一般很好地處理流事件。和所有的教程一樣,本文不可能涵蓋到異步編程的所有細節,但是若能對你有所啟發,那就是再好不過了。

7. 參考鏈接

8.作者簡介

邱仁博,多年運營商商業分析、數據中心數據庫方向工作經驗,現任職于某地市事業單位信息技術部。日常關注國內外極客新聞、前后端技術。海外知識搬運工。

【51CTO原創稿件,合作站點轉載請注明原文作者和出處為51CTO.com】

 

責任編輯:龐桂玉 來源: 51CTO
相關推薦

2019-11-05 10:03:08

callback回調函數javascript

2017-05-11 20:20:59

JavascriptPromiseWeb

2021-06-07 09:44:10

JavaScript開發代碼

2021-12-10 07:47:30

Javascript異步編程

2025-03-24 07:20:00

2021-01-14 07:52:24

JavaScript回調函數

2017-08-28 15:21:29

異步處理回調函數異步編程

2021-06-06 19:51:07

JavaScript異步編程

2015-10-26 09:25:42

2009-08-21 17:02:20

ASP.NET異步回調

2023-06-29 07:48:35

異步加載JavaScript

2023-11-08 13:18:00

JestJavaScript框架

2016-09-18 21:14:54

JavascriptPromiseWeb

2012-02-01 10:33:59

Java

2023-08-23 13:24:00

異步編程方法

2022-01-04 20:52:50

函數異步Promise

2009-11-09 15:58:07

WCF回調方法

2023-04-28 15:20:37

JavaScript事件循環

2022-04-12 08:30:52

回調函數代碼調試

2011-07-25 14:32:40

Cocoa 框架 函數
點贊
收藏

51CTO技術棧公眾號

国产精品裸体一区二区三区| 久久香蕉国产线看观看av| 国内外成人激情视频| 日本电影一区二区在线观看| 久久最新视频| 久久久av电影| 给我免费观看片在线电影的| 午夜激情成人网| 自拍偷自拍亚洲精品播放| 亚洲伊人久久综合| 超碰超碰超碰超碰| 91精品高清| 亚洲跨种族黑人xxx| 最新av免费在线观看| 国内激情视频在线观看| 国产精品久久久久久妇女6080| 国产福利久久精品| 日韩精品一区不卡| 欧美日韩一区二区国产| 一区二区三欧美| 免费黄色三级网站| 日日夜夜一区| 色综合咪咪久久| 97在线免费视频观看| 国产精品秘入口| 成人黄色av电影| 国产在线高清精品| 91玉足脚交嫩脚丫在线播放| 综合天天久久| 日韩最新在线视频| 一本加勒比北条麻妃| 日韩av综合| 欧美日韩国产综合草草| 人妻无码久久一区二区三区免费| 日本天堂在线观看| 久久久久久97三级| 韩国成人av| 亚洲精品网站在线| 国模一区二区三区白浆| 国产成人av在线播放| 日本免费观看视| 欧美激情视频一区二区三区免费| 精品国偷自产在线视频| 欧美黄色高清视频| 国产成人久久| 亚洲人成电影网站| 国产在线不卡av| 中文字幕日韩在线| 日韩三区在线观看| 国产毛片久久久久久| 欧美午夜三级| 欧美天堂亚洲电影院在线播放| 久久精品.com| 一区二区精品伦理...| 舔着乳尖日韩一区| 国产免费黄色一级片| 成人女同在线观看| 一区二区三区精密机械公司| 99视频精品全部免费看| huan性巨大欧美| 亚洲欧美日韩电影| 无码人妻aⅴ一区二区三区日本| 日本高清中文字幕在线| 中文字幕中文字幕一区二区| 中文字幕精品—区二区日日骚| 91caoporm在线视频| 国产精品免费久久久久| 在线观看免费91| 精精国产xxxx视频在线| 亚洲色大成网站www久久九九| 夜夜爽www精品| 久久久久久久久免费视频| 亚洲日本成人在线观看| 国产精品一二三在线观看| 精品日韩av| 精品久久久久久国产| 欧美 日韩精品| 国语自产精品视频在线看抢先版结局 | 成人毛片在线| 中文字幕免费精品一区| 国产精品嫩草影院俄罗斯| 欧美99久久| 国外成人在线直播| 国产无遮挡呻吟娇喘视频| 日韩av二区在线播放| 成人网欧美在线视频| 亚洲精品中文字幕成人片 | 精品视频全国免费看| 色婷婷.com| 91欧美极品| 亚洲精品一区二三区不卡| 日本成人免费视频| 中文在线日韩| 欧美在线观看日本一区| 影音先锋国产在线| 国产成人精品一区二| 老司机精品福利在线观看| 日本精品一区二区三区在线播放| 亚洲h在线观看| 天天操天天摸天天爽| 午夜日韩影院| 亚洲三级av在线| 日韩激情小视频| 国产午夜久久| 成人免费网站在线观看| 亚洲av成人无码久久精品老人| 国产精品视频yy9299一区| 成人免费看片'免费看| 亚洲高清黄色| 亚洲第一偷拍网| а天堂中文在线资源| 日韩亚洲精品在线| 成人免费在线网址| 欧美挠脚心网站| 一级日本不卡的影视| 免费看污污网站| 乱亲女h秽乱长久久久| 精品国产一区二区三区四区在线观看 | 欧美视频综合| 亚洲在线一区二区三区| av视屏在线播放| 国产96在线亚洲| 久久精品国产亚洲一区二区| 少妇高潮av久久久久久| 成人小视频免费在线观看| 天天综合中文字幕| 高清电影一区| 日韩高清av一区二区三区| 欧美成人黄色网| 蜜桃久久久久久| 欧美一区激情视频在线观看| 国产乱码午夜在线视频| 精品免费视频一区二区| 黄色片子在线观看| 热久久一区二区| 欧美精品一区二区三区在线四季| 欧美24videosex性欧美| 51精品秘密在线观看| 国产传媒在线看| 视频在线观看一区二区三区| 久久久人人爽| 极品av在线| 亚洲第一区中文字幕| 国产无套粉嫩白浆内谢| 国产成人精品1024| 91视频成人免费| 国产美女亚洲精品7777| 久久黄色av网站| 国产精品探花视频| ●精品国产综合乱码久久久久| 亚洲欧洲日本精品| 99久久人爽人人添人人澡| 欧美成人精品影院| 国产叼嘿视频在线观看| 亚洲免费资源在线播放| 国产91在线免费观看| 亚洲天堂一区二区三区四区| 亚洲free嫩bbb| 性国产高清在线观看| 日韩免费福利电影在线观看| 久草视频在线免费看| 国产成人免费视频精品含羞草妖精| 天天干天天色天天爽| 精品亚洲a∨一区二区三区18| 美女999久久久精品视频| 北条麻妃一二三区| 亚洲大片一区二区三区| 大黑人交xxx极品hd| 久久综合激情| 影音先锋欧美资源| 亚洲日本va中文字幕| 久久久亚洲精选| 色播色播色播色播色播在线| 欧洲精品一区二区| 国精产品久拍自产在线网站| 国产盗摄视频一区二区三区| 男女视频网站在线观看| 欧美欧美黄在线二区| 国产欧美日韩91| 污污的视频在线观看| 日韩精品福利网站| 日本妇乱大交xxxxx| 亚洲男同1069视频| 中文字幕a在线观看| 视频一区中文字幕| 国产精品88久久久久久妇女 | 日本午夜精品一区二区三区| 2019中文亚洲字幕| 97香蕉久久超级碰碰高清版| 2021av在线| 精品乱码亚洲一区二区不卡| 欧美一级片免费在线观看| 欧美激情一二三区| 中文字幕欧美视频| 亚洲一区二区三区高清不卡| 一区二区三区三区在线| 老司机在线精品视频| 国产精品自产拍在线观看| 欧美理论片在线播放| 国产一区二区三区中文| 性一交一乱一乱一视频| 91豆麻精品91久久久久久| 91视频综合网| 91蜜桃婷婷狠狠久久综合9色| www.国产视频.com| 免费视频一区| 国产91在线亚洲| 日韩一区电影| 裸模一区二区三区免费| 久久69av| 国产欧美一区二区三区在线看 | 精品国产免费久久久久久婷婷| 久久婷婷麻豆| 日韩一级性生活片| 五月精品视频| 日韩精品在在线一区二区中文| 一区二区三区在线免费看| 国产成人精品一区二区| 高清电影在线免费观看| 精品国偷自产在线视频| 大胆av不用播放器在线播放 | 国产精品v亚洲精品v日韩精品| 色综合视频二区偷拍在线| 欧美美女在线直播| 91深夜福利视频| 99久久精品一区二区成人| 国产91精品久久久久久久| 污视频网站免费在线观看| 最近更新的2019中文字幕| 欧美日韩激情视频一区二区三区| 亚洲成人久久久久| 亚洲第一色网站| 在线播放日韩导航| 在线播放亚洲精品| 在线视频观看一区| 天堂网中文字幕| 精品国产乱码久久久久久天美 | 国产精品伦子伦| 国产成人精品www牛牛影视| 亚洲精品乱码久久久久久动漫| 美女mm1313爽爽久久久蜜臀| 人妻丰满熟妇av无码区app| 国产精品三上| 免费成人在线视频网站| 99热这里只有精品8| 九九热只有这里有精品| 黄色精品网站| 国产3p露脸普通话对白| 日韩视频中文| 日本一本二本在线观看| 视频一区二区国产| 亚洲精品高清无码视频| 日韩专区在线视频| 国产成人手机视频| 日本成人在线视频网站| 午夜激情av在线| 久久99精品久久久久久久久久久久 | 欧美刺激午夜性久久久久久久| 精品人妻aV中文字幕乱码色欲| 777亚洲妇女| 国产夫绿帽单男3p精品视频| 日韩欧美在线一区二区三区| 亚洲精选一区二区三区| 亚洲成人激情图| 免费a在线观看| 色午夜这里只有精品| 超碰porn在线| 久久久久久久亚洲精品| 少妇视频一区| 国产精品美女av| 95精品视频| 国产精品v欧美精品v日韩| 青青草原在线亚洲| 日韩精品久久久免费观看| 欧美国产一区二区三区激情无套| 91免费视频黄| 伊人久久亚洲美女图片| 国产无套内射久久久国产| 蜜桃精品视频在线观看| 一区二区在线免费观看视频| 26uuu亚洲综合色欧美 | 五月天激情开心网| 亚洲人成在线电影| 久久77777| 97在线视频一区| 黄色精品视频| 999国内精品视频在线| 日韩成人一级| 中文字幕中文字幕在线中一区高清| 国产精品啊v在线| 人妻丰满熟妇av无码区app| 国产乱子伦视频一区二区三区| 久久久久久久久久影视| 国产午夜精品理论片a级大结局| 国产成人自拍网站| 狠狠躁夜夜躁人人躁婷婷91 | 精品国产一区二区三区四区四| 国产三级在线看| 美日韩精品免费视频| 美女福利一区二区三区| 亚洲自拍偷拍网址| 国产aⅴ精品一区二区三区久久| 伊人网在线免费| 日韩和欧美的一区| 久久久久久久久久久影视| 91亚洲男人天堂| 国产高潮国产高潮久久久91 | 99久久亚洲精品日本无码| 亚洲精品视频免费在线观看| 成人在线影视| 国产精品久久久久秋霞鲁丝| 日日狠狠久久偷偷综合色| 精品国产三级a∨在线| 日日欢夜夜爽一区| 亚洲国产精品无码久久久久高潮| 亚洲品质自拍视频| 青青国产在线视频| 亚洲国产精品99久久| 免费观看成人高潮| 国产成人亚洲综合91| 久久久伦理片| 亚洲精品少妇一区二区| 久久69国产一区二区蜜臀| 伊人网在线视频观看| 午夜视频在线观看一区二区三区| 99国产精品99| 神马久久久久久| 精品视频在线一区二区在线| 久久免费视频1| 99精品国产福利在线观看免费 | 中文字幕在线观看高清| 亚洲欧美国产制服动漫| 九色porny自拍视频在线播放| 成人午夜电影免费在线观看| 2023国产精品久久久精品双| 国产又黄又猛又粗| 国产亚洲成av人在线观看导航| 中文字幕在线观看免费视频| 亚洲第一av网| av资源在线| 国产三区精品| 在线观看一区视频| 欧美丰满熟妇bbb久久久| 亚洲激情av在线| www.黄色av| 欧美黄色片在线观看| 日韩免费精品| 超碰成人免费在线| 成人av在线资源网| 日韩av综合在线| 日韩电影中文字幕在线| 九色porny自拍视频在线播放 | 韩国三级丰满少妇高潮| 亚洲欧美日韩国产手机在线| 国产伦理一区二区| 麻豆一区二区在线观看| 日韩成人精品| 日韩成人三级视频| 成人av网在线| 国产一级片毛片| 国产一区二区激情| 久久精品国产精品亚洲毛片| 中文字幕制服丝袜在线| 国产乱妇无码大片在线观看| 久久99久久98精品免观看软件| 日韩欧美国产麻豆| 55av亚洲| 区一区二区三区中文字幕| 奇米一区二区三区| 黄色录像二级片| 精品国产电影一区二区| a一区二区三区| 亚洲一区美女| 国产成人免费视频精品含羞草妖精| 国产午夜小视频| 亚洲一区av在线播放| 亚洲综合资源| 青青在线免费观看| av不卡在线观看| 国产精品露脸视频| 欧美不卡视频一区发布| 欧美午夜18电影| 亚洲77777| 亚洲电影第三页| 国产精品四虎| 成人综合色站| 首页国产欧美日韩丝袜| 在线观看成人毛片| 亚洲激情视频在线观看| yy6080久久伦理一区二区| a级片一区二区| 久久精品一区蜜桃臀影院| 国产区精品在线| 欧美在线观看网站| 91精品蜜臀一区二区三区在线| 日本国产在线视频| 欧美视频一区二| 97人澡人人添人人爽欧美| 亚洲精品高清视频| av不卡免费电影|