異步代碼
一、Promise
promise表示一個異步操作的最終結果,可以理解為狀態機,它存在三種不同的狀態:
(1)Pending:表示還在執行。
(2)Fulfilled(或者resolved):執行成功。
(3)Rejected:執行失敗。
1、將異步方法封裝成Promise
1 //Promise的構造函數
2 var promise = new Promise(function (resolve,reject) {
3 if(/*異步操作成功*/){
4 resolve(value);
5 }else{
6 reject(value);
7 }
8 })
9 //使用Promise封裝的readFile
10 var fs = require("fs");
11 function readFile_promise(path) {
12 return new Promise(function(resolve, reject) {
13 fs.readFile(path, "utf-8", function(err, data) {
14 if (data) {
15 resolve(data);
16 } else {
17 reject(err);
18 }
19 });
20 });
21 }
其中,resolve和reject是兩個函數,resolve函數會在異步操作成功完成時被調用,並將異步操作的返回值作為參數傳遞到外部;reject則在異步操作出現異常時被調用,會將錯誤信息作為參數傳遞出去。即resolve和reject方法沒有做多余的操作,僅僅把異步的結果傳遞出去而已,對於異步結果,是交給then方法來完成的。
2、使用then方法獲取結果
1 //處理結構
2 promise.then(function (data) {
3 //success
4 },function (error) {
5 //failure
6 })
7 var pro = readFile_promise(‘foo.txt‘);
8 pro.then(function(value) {
9 console.log(value);
10 return readFile_promise(‘bar.txt‘);
11 }).then(function(value) {
12 console.log(value);
13 }).catch(function(err) {
14 console.log(err);
15 });//捕獲異常
3、Promise的常用API
(1)Promise.resolve:將一個給Promise對象轉化為Promise對象。但resolve不能轉換一個異步方法,例如readFile方法。
1 var obj = {
2 then: function() {
3 console.log("I am a then method");
4 }
5 }
6 Promise.resolve(obj); //轉化後的Promise會自動執行其的then方法
7 //I am a then method
8 var p = Promise.resolve("Hello World");
9 p.then(function (result) {
10 console.log(result);
11 }); //Hello World
(2)Promise.reject:也是返回一個Promise對象,不同之處在於這個Promise的狀態為reject,reject方法的參數會作為錯誤信息傳遞給then方法。
(3)Promise.all:將多個Promise對象包裝成一個Promise。當調用Promise.all時,所有的Promise都已經開始執行了,all方法只是等到全部的Promise完成後,對所有的執行結果做一下包裝再返回。
1 var promises = ["foo.txt", "bar.txt", "baz.txt"].map(function(path) {
2 return readFile_promise(path);
3 });
4 Promise.all(promises).then(function(results) {
5 console.log(results); //results的內容是文本文件內容的順序排列
6 }).catch(function(err) {
7
8 });
(4)Promise.race:接收一個Promise數組作為參數並返回一個新的Promise,數組中的Promise會同時開始執行,race返回的Promise的狀態由數組中率先執行完畢的Promise的狀態決定。
(5)Promise.catch:Promise在執行中如果出了錯誤,可以使用throw關鍵字拋出錯誤,並且可以使用catch方法進行去捕獲;如果不設置任何回調函數捕獲錯誤,Promise內部拋出的錯誤就無法傳遞到外部。
1 var promise = new Promise(function(resolve, reject) {
2 throw new Error("get error");
3 });
4 //如果不設置catch函數,上面即使拋出error也不會使進程退出。
5 promise.catch(function(error) {
6 console.log(error);
7 });
4、Promise的不足:Promise的一堆鏈式調用會讓人看起來很麻煩。可以考慮將then方法的回調函數抽取出來。
二、Generator
我們無法控制Promise的執行,新建一個Promise後,其狀態自動轉化為pending,同時開始執行,直到狀態改變後我們才能進行下一步操作。而Generator函數不同,Generator函數可以由用戶執行中斷或者恢復執行的操作,Generator中斷後可以轉去執行別的操作,然後再回頭從中斷的地方恢復執行。
Generator函數和普通函數在外表上最大的區別有兩個:
(1)在function關鍵字和方法名中間有個星號(*)。
(2)方法體中使用“yield”關鍵字。
1 var fs = require("fs");
2
3 function readFile_promise(path) {
4 return new Promise(function(resolve, reject) {
5 fs.readFile(path, "utf-8", function(err, data) {
6 if (data) {
7 resolve(data);
8 } else {
9 reject(err);
10 }
11 });
12 });
13 }
14
15 function* gen() {
16 var result = yield readFile_promise(‘../baz.txt‘);
17 console.log(result);
18 }
19 var g = gen();
20 var result = g.next();
21 //console.log(result);
22 result.value.then(function(data) {
23 g.next(data);
24 });
之所以可以使用Generator函數來處理異步任務,原因有二:
(1)Generator函數可以中斷和恢復執行,這個特性有yield關鍵字來實現。
(2)Generator函數內外可以交換數據,這個特性由next函數來實現。
即Generator函數處理異步任務的核心思想:先將函數暫停在某處,然後拿到異步操作的結果,然後再把這個結果傳到方法體內。
三、async
async函數可以看作是自帶執行器的Generator函數。await關鍵字後面往往是一個Promise,如果不是就隱式調用Promise.resolve來轉換成一個Promise。
1 var fs = require("fs");
2 async function asyncFunc() {
3
4 return "hello node";
5 }
6 asyncFunc().then(function(data) {
7 console.log(data);
8 });
9
10 function readFile_promise(path) {
11 return new Promise(function(resolve, reject) {
12 fs.readFile(path, "utf-8", function(err, data) {
13 if (data) {
14 resolve(data);
15 } else {
16 reject(err);
17 }
18 });
19 });
20 }
21
22 aysnc
23
24 function readFile() {
25 var result = await readFile_promise("../foo.txt");
26 console.log(‘test‘);
27 }
28 readFile();
async函數的優點:可以實現自動執行,無須借助第三方模塊等,也免去了Generator函數中一些復雜的概念,async函數的聲明和執行與普通同步函數幾乎一模一樣(除了async和await關鍵字外)。
async函數也有一些不足的地方,如果我們有很多層的方法調用,最底層的異步操作被封裝成了async方法,那麽該函數的所有上層方法可能都要變成async方法。
異步代碼