ECMAScript 6 新增了正式的 Promise(期約)參照型別,支援優雅地定義和組織非同步邏輯。接下來幾個版本增加了使用 async 和 await 關鍵字定義非同步函數的機制
JavaScript 是單執行緒事件迴圈模型。非同步行為是為了優化因計算量大而時間長的操作,只要你不想為等待某個非同步操作而阻塞執行緒執行,那麼任何時候都可以使用
JS中同步任務會立即執行並放入呼叫棧中,而非同步任務會被放入事件佇列中,等待呼叫棧中的任務執行完畢後再被推入呼叫棧中執行。當非同步任務被推入呼叫棧中執行時,它就變成了同步任務。這種機制被稱為事件迴圈。
同步行為對應記憶體中順序執行的處理器指令。每條指令都會嚴格按照它們出現的順序來執行,而每條指令執行後也能立即獲得儲存在系統本地(如暫存器或系統記憶體)的資訊。這樣的執行流程容易分析程式在執行到程式碼任意位置時的狀態(比如變數的值)。
例如
let x = 3;
x = x + 4;
在程式執行的每一步,都可以推斷出程式的狀態。這是因為後面的指令總是在前面的指令完成後才會執行。等到最後一條指定執行完畢,儲存在 x 的值就立即可以使用。
非同步行為類似於系統中斷,即當前程序外部的實體可以觸發程式碼執行。非同步程式碼不容易推斷
例如,在定時回撥中執行一次簡單的數學計算:
let x = 3;
setTimeout(() => x = x + 4, 1000);
這段程式雖與上面同步程式碼執行的任務一樣,都是把兩個數加在一起,但這一次執行執行緒不知道 x 值何時會改變,因為這取決於回撥何時從訊息佇列出列並執行。雖然這個例子對應的低階程式碼最終跟前面的例子沒什麼區別,但第二個指令塊(加操作及賦值操作)是由系統計時器觸發的,這會生成一個入隊執行的中斷。到底什麼時候會觸發這個中斷,這對 JavaScript 執行時來說是一個黑盒
非同步程式設計主要包含以下幾類,非同步程式設計傳統的解決方案是回撥函數,這種傳統的方式容易導致「回撥地獄」,即函數多層巢狀,讓程式碼難以閱讀,維護困難,空間複雜度大大增加
require('fs').readFile('./index.html', (err,data)=>{})
$.get('/server', (data)=>{})
setTimeout(()=>{}, 2000);
Promise屬於ES6規範, 是非同步程式設計的一種解決方案,比傳統的解決方案——回撥函數和事件——更合理和更強大。期約故意將非同步行為封裝起來,從而隔離外部的同步程式碼
const p=new Promise(()=>{})
console.log(p);
Promise 建構函式: Promise (excutor)
let p = new Promise((resolve, reject) => {
// 同步呼叫
console.log(111);
});
console.log(222);
期約是一個有狀態的物件,Promise範例物件中的屬性PromiseState儲存著該Promise範例的狀態,可能處於如下 3 種狀態之一:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
之所以說是應付直譯器,是因為如果不提供執行器函數,就會丟擲 SyntaxError。
無論 resolve()和 reject()中的哪個被呼叫,狀態轉換都不可復原了。於是繼續修改狀態會靜默失敗,如下所示:
let p = new Promise((resolve, reject) => {
resolve();
reject(); // 沒有效果
});
setTimeout(console.log, 0, p); // Promise <resolved>
期約主要有兩大用途:
let p1 = new Promise((resolve, reject) => resolve());
setTimeout(console.log, 0, p1); // Promise <resolved>
let p2 = new Promise((resolve, reject) => reject());
setTimeout(console.log, 0, p2); // Promise <rejected>
// Uncaught error (in promise)
所謂Promise
,簡單說就是一個容器,裡面儲存著某個未來才會結束的事件(通常是一個非同步操作)的結果。從語法上說,Promise 是一個物件,從它可以獲取非同步操作的訊息。Promise 提供統一的 API,各種非同步操作都可以用同樣的方法進行處理
有了Promise
物件,就可以將非同步操作以同步操作的流程表達出來,避免了層層巢狀的回撥函數。此外,Promise
物件提供統一的介面,使得控制非同步操作更加容易
Promise.prototype.then([onResolved|null],[onRejected|null])是為期約範例新增處理程式的主要方法。
引數:onResolved 處理程式和 onRejected 處理程式。這兩個引數都是可選的,如果提供的話,則會在期約分別進入「兌現」和「拒絕」狀態時執行。
返回值:一個新的Promise物件
可以指定多個回撥,當promise物件變為相應的狀態的時候就都會執行
let p = new Promise((resolve, reject) => {
resolve('OK');
});
///指定回撥 - 1
p.then(value => {
console.log(value);
});
//指定回撥 - 2
p.then(value => {
alert(value);
});
問:then()返回的Promise物件的狀態是如何決定的呢?
① 如果丟擲異常, 新 promise 變為 rejected, reason 為丟擲的異常
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//執行 then 方法
let result = p.then(value => {
console.log(value);
//1. 丟擲錯誤
throw '出了問題';
}, reason => {
console.warn(reason);
});
② 如果返回的是非 promise 的任意值, 新 promise 變為 resolved, value 為返回的值
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//執行 then 方法
let result = p.then(value => {
console.log(value);
//2. 返回結果是非 Promise 型別的物件
return 521;
}, reason => {
console.warn(reason);
});
console.log(result);
③ 如果返回的是另一個新 promise, 此 promise 的結果就會成為新 promise 的結果
let p = new Promise((resolve, reject) => {
resolve('ok');
});
//執行 then 方法
let result = p.then(value => {
console.log(value);
//3. 返回結果是 Promise 物件
return new Promise((resolve, reject) => {
// resolve('success');
reject('error');
});
}, reason => {
console.warn(reason);
});
console.log(result);
不管呼叫的是onResolved還是onRejected函數,返回的新promise物件主要由返回值決定(丟擲錯誤的情況除外)
問:promise 如何串連多個操作任務?
(1) promise 的 then()返回一個新的 promise, 可以形成 then()的鏈式呼叫
(2) 通過 then 的鏈式呼叫串連多個同步/非同步任務
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
const result=p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})
console.log(result);
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}) // Promise (resolved): 'success'
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
})// Promise (resolved): undefined 因為沒有返回值也沒有丟擲錯誤,所以值是undefined, 因為undefined不是Promise物件,所以返回的Promise狀態為resolved
p.then(value => {
return new Promise((resolve, reject) => {
resolve("success");
});
}).then(value => {
console.log(value);
}).then(value => {
console.log(value);
})// Promise (resolved): undefined
中斷then()鏈
方法:在then()的回撥函數中返回一個 pendding 狀態的 promise 物件。因為pendding狀態的promise物件不會觸發onResolved()或onRejected()函數
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
p.then(value => {
console.log(111);
//有且只有一個方式
return new Promise(() => {});//
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
穿透:當沒有指定相應Promise狀態的回撥函數時,就可以跳過執行該then()
練習:分析輸出結果
1.
let p = new Promise((resolve, reject) => {
setTimeout(() => {
// resolve('OK');
reject('Err');
}, 10);
});
p.then(null,reason => {
console.log(111);
}).then(null,reason => {
console.log(222);
}).then(null,reason => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
分析:p.then(null,reason => {
console.log(111);
})的返回值是Promise (resolved):undefined, 後面沒有對應的onResolved的回撥函數,就跳過了執行,所以控制檯只輸出了111
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
// reject('Err');
}, 10);
});
// console.log(p.then(value => {
// console.log(111);
// },reason=>{
// throw '失敗啦!';
// })==p.then(value => {
// console.log(111);
// },reason=>{
// throw '失敗啦!';
// }).then(value => {
// console.log(222);
// })); // false
console.log(p.then(value => {
console.log(111);
},reason=>{
throw '失敗啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}));
p.then(value => {
throw '失敗啦!';
}).then(value => {
console.log(222);
}).then(value => {
console.log(333);
}).catch(reason => {
console.warn(reason);
});
p.then(value => {
throw '失敗啦!';
})返回Promise (rejected): '失敗啦!',後面沒有對應的onRejected()回撥函數,就跳過了執行,所以就直接執行catch()來捕獲錯誤資訊
Promise.prototype.catch(onRejected)
catch()基於then()做了一個單獨的封裝,只接收rejected狀態的回撥函數
給Promise物件設定回撥函數的方法有Promise.prototype.then()和Promise.prototype.catch(),注意Promise.prototype.catch()只能指定錯誤的回撥函數
靜態方法,不是實體方法
期約並非一開始就必須處於待定狀態,然後通過執行器函數才能轉換為落定狀態。通過呼叫Promise.resolve()靜態方法,可以範例化一個解決的期約。
下面兩個期約範例實際上是一樣的:
let p1 = new Promise((resolve, reject) => resolve());
let p2 = Promise.resolve();
引數:成功的資料或 promise 物件
說明: 返回一個成功/失敗的 promise 物件
let p1 = Promise.resolve(521);
console.log(p1);
let p = Promise.resolve(new Error('foo'));
setTimeout(console.log, 0, p); // Promise <resolved>: Error: foo
let p = Promise.resolve(7);
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p))); // true
// 這個冪等性會保留傳入期約的狀態:
let p = new Promise(() => {});
setTimeout(console.log, 0, p); // Promise <pending>
setTimeout(console.log, 0, Promise.resolve(p)); // Promise <pending>
setTimeout(console.log, 0, p === Promise.resolve(p)); // true
let p2 = Promise.resolve(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
let p2 = Promise.resolve(new Promise((resolve, reject) => {
reject('Error');
}));
console.log(p2);
p2.catch(reason => {
console.log(reason);
})
與 Promise.resolve()類似,Promise.reject()會範例化一個拒絕的期約並丟擲一個非同步錯誤(這個錯誤不能通過 try/catch 捕獲,而只能通過拒絕處理程式捕獲)。
下面的兩個期約範例實際上是一樣的:
let p1 = new Promise((resolve, reject) => reject());
let p2 = Promise.reject();
引數:失敗的理由或 promise 物件
說明: 返回一個失敗的 promise 物件
let p1 = Promise.reject(521);
console.log(p1);
let p2 = Promise.reject(new Promise((resolve, reject) => {
resolve('OK');
}));
console.log(p2);
引數: 包含 n 個 promise 的陣列
說明: 返回一個新的 promise, 只有所有的 promise 都成功該promise才成功,成功結果為所有promise成功結果組成的陣列。只要有一個promise失敗了該promise就失敗,失敗理由為第一個失敗的promise的失敗理由
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
const result = Promise.all([p1, p2, p3]);
console.log(result);
let p1 = new Promise((resolve, reject) => {
resolve('OK');
})
let p2 = Promise.reject('Error1');
let p3 = Promise.reject('Error2')
const result = Promise.all([p1, p2, p3]);
console.log(result);
通過這種方式,可以檢測頁面中某個請求是否超時,並輸出相關的提示資訊。
引數: 包含 n 個 promise 的陣列
說明: 返回一個新的 promise, 該promise等於第一個完成的 promise(即第一個確定狀態的promise)
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
})
let p2 = Promise.resolve('Success');
let p3 = Promise.resolve('Oh Yeah');
//呼叫
const result = Promise.race([p1, p2, p3]);
console.log(result);
let p = new Promise((resolve, reject) => {
//1. resolve 函數
// resolve('ok'); // pending => fulfilled (resolved)
//2. reject 函數
// reject("error");// pending => rejected
//3. 丟擲錯誤
// throw '出問題了';// pending => rejected
});
console.log(p);
2. 呼叫實體方法Promise.resolve()或Promise.reject()
ES6 規定,Promise
物件是一個建構函式,用來生成Promise
範例
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 非同步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
Promise
建構函式接受一個函數作為引數,該函數的兩個引數分別是resolve
和reject
。它們是兩個函數,由 JavaScript 引擎提供,不用自己部署
Promise
範例生成以後,可以用then
方法分別指定resolved
狀態和rejected
狀態的回撥函數。
promise.then(function(value) {
// success
}, function(error) {
// failure
});
下面是一個抽獎範例
<body>
<button id="btn">點選抽獎</button>
<script>
const btn=document.getElementById("btn")
btn.onclick=function(){
// 每次點選按鈕建立一個Promise範例物件
const p =new Promise((resolve,reject)=>{
setTimeout(()=>{
let n =parseInt(Math.random()*101)//取值[1,100]的整數
if(n<30){
resolve(n)
}else{
reject(n)
}
},10)
})
p.then((value)=>{
alert("恭喜中獎!中獎數位為"+value);
},(reason)=>{
alert("再接再厲! 中獎數位為"+reason);
})
}
</script>
</body>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div></div>
<script>
function loadImageAsync(url) {
var promise = new Promise(function (resolve, reject) {
const image = new Image();
image.onload = function () {
resolve(image);
};
image.onerror = function () {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
return promise;
}
loadImageAsync("http://iwenwiki.com/api/vue-data/vue-data-1.png")
.then(function(data){
console.log(data);
$("div").append(data)
},function(error){
$("div").html(error)
})
</script>
</body>
</html>
實時效果反饋
1. Promise
的作用是什麼,下列描述正確的是:
A Promise
是非同步程式設計的一種解決方案,可以將非同步操作以同步操作的流程表達出來
B Promise
是同步程式設計的一種解決方案,可以將同步操作以非同步操作的流程表達出來
C Promise
使得控制同步操作更加容易
D Promise
還不是ES6的標準,目前是社群版本
答案
1=>A
Promise封裝Ajax,讓網路請求的非同步操作變得更簡單
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const getJSON = function (url) {
const promise = new Promise(function (resolve, reject) {
const handler = function () {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php").then(function (json) {
console.log(json);
}, function (error) {
console.error('出錯了', error);
});
</script>
</body>
</html>
傳入一個遵循常見的錯誤優先的回撥風格的函數(即以(err, value)=>{...}回撥作為最後一個引數),並返回一個返回值為promise物件的函數版本
「錯誤優先」是指回撥函數中,錯誤引數為回撥函數的第一個引數。node.js環境中fs模組中的非同步api大多是這種風格的
引數:函數
返回值:函數,返回的該函數的返回值是promise物件
將函數promise化的好處:函數promise化之後,函數的返回值就變成了promise物件,這樣就可以呼叫promise的實體方法then()或catch(), 利用它們的鏈式寫法,就能避免回撥函數多層巢狀
範例:呼叫util.promisefy()函數,將fs.readFile()函數promise化
//引入 util 模組
const util = require('util');
//引入 fs 模組
const fs = require('fs');
//promise化fs.readFile()函數
let mineReadFile = util.promisify(fs.readFile);
// promise化之後就可以呼叫promise範例的then()或catch()方法
mineReadFile('./resource/content.txt').then(value=>{
console.log(value.toString());
});
在實際應用中,一個函數滿足這幾個條件,就可以被 promisify 化:
// promisefy()返回一個函數,返回的該函數的返回值是Promise物件
// fn指要promise化的函數
const promisefy = (fn) => {
// ‘...args’表示剩餘引數
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{
if(err) reject(err);
resolve(data)
})
})
}
}
範例(第14屆藍橋杯省賽第三期模擬題)
題目
下面就請你以 Node.js 中常用的讀取檔案操作為例,封裝一個 Promisefy 函數,將回撥形式呼叫的讀取檔案方法轉換成一個 Promise 的版本。
目錄結構如下:
請在 index.js 檔案中的補全程式碼,完成 promisefy 函數的封裝。將 fs 中的 readFile 方法 promise 化。也就是說 readFileSync 方法執行後,會返回一個 promise,可以呼叫 then 方法執行成功的回撥或失敗的回撥。
在控制檯執行:node index
, 此時應列印出 true,即:回撥形式的 fs.readFile 方法讀取同個檔案的結果與 Promise 形式讀取結果一致。
參考答案
const fs = require('fs')
const path = require('path')
const textPath = path.join(__dirname, '/test.md')
// 讀取範例檔案
fs.readFile(textPath, 'utf8', (err, contrast) => {
// 通過promisefy轉化為鏈式呼叫
const readFileSync = promisefy(fs.readFile)
readFileSync(textPath, 'utf8')
.then((res) => {
console.log(res === contrast) // 此處結果預期:true,即promise返回內容與前面讀取內容一致
})
.catch((err) => {})
})
const promisefy = (fn) => {
// TODO 此處完成該函數的封裝
// ‘...args’表示剩餘引數
return function(...args){
return new Promise((resolve,reject)=>{
fn(...args,(err,data)=>{
if(err) reject(err);
resolve(data)
})
})
}
}
module.exports = promisefy // 請勿刪除該行程式碼
// 在非同步任務中不能丟擲錯誤,即使是內建的Promise也捕獲不到
class Promise{
constructor(executor){
// 設定預設的PromiseState和PromiseResult
// 因為Promise()建構函式是以範例的方式呼叫(new運運算元),所以this指向範例
this.PromiseState='pendding'
this.PromiseResult=null
// 將callback物件定義到範例內部,用來儲存後面通過then()或catch()指定的onResolved()和onRejected()函數,這是考慮到executor中通過非同步任務改變promise範例狀態和多個then()指定了多個onResolved()或onRejected()回撥的情況
this.callbacks=[];
// 而resolve()和reject()是以函數形式呼叫的,this為window物件,所以這裡用self儲存指向範例的this
const self=this;
function resolve(data){
// 該判斷為了避免狀態重複改變
if(self.PromiseState!=='pendding') return;
self.PromiseState='fulfilled'//或'resolved'
self.PromiseResult=data
// 注意要在Promise範例狀態改變並且資料賦值之後呼叫onResolved
// 注意在呼叫前要判斷有沒有定義onResolved()函數,所以就看callbacks陣列第一個元素有沒有onResolved屬性
// if(self.callbacks[0].onResolved){
// 通過setTimeout將then中的回撥設定成非同步, 因為內建的Promise的實體方法then()中的回撥是非同步的
setTimeout(()=>{
self.callbacks.forEach(item => {
item.onResolved(data)
})
});
// }
};
function reject(data){
// 該判斷為了避免狀態重複改變
if(self.PromiseState!=='pendding') return;
self.PromiseState='rejected'
self.PromiseResult=data
// 注意要在Promise範例狀態改變並且資料賦值之後呼叫onRejected
// 注意在呼叫前要判斷有沒有定義onRejected()函數,所以就看callbacks陣列第一個元素有沒有onRejected屬性
// if(self.callbacks[0].onRejected){
// 通過setTimeout將then中的回撥設定成非同步, 因為內建的Promise的實體方法then()中的回撥是非同步的
setTimeout(()=>{
self.callbacks.forEach(item => {
item.onRejected(data)
})
})
// }
};
try{
// executor在傳入時就能確定是個函數,所以這裡不需要再定義executor, 只是呼叫即可
executor(resolve,reject)
}catch(e){
reject(e)
}
}
//新增 then 方法
then(onResolved, onRejected){
self=this
// 考慮異常穿透情況
if(typeof onRejected!=='function'){
onRejected=reason=>{
throw reason;
}
}
if(typeof onResolved!=='function'){
onResolved=value=>value // (value)=>{return value}的簡寫形式
}
//
return new Promise((resolve,reject)=>{
// 這個callback()函數得放到返回的Promise範例裡,否則會報錯,說resolve和reject未定義
function callback(type){
try{
// 因為callback()是以函數形式呼叫的,此時this指向window物件,所以此處引數不能為this.PromiseResult
let result=type(self.PromiseResult)
if(result instanceof Promise){
result.then(v=>{
resolve(v)
},e=>{
reject(e)
})
}else{// 丟擲錯誤時result是undefined還是null? 不進入該分支應該
resolve(result)
}
}catch(e){//考慮丟擲錯誤的情況
reject(e)
}
}
// 考慮executor中同步任務改變promise範例狀態
if(this.PromiseState==='fulfilled'){
// 通過setTimeout將then中的回撥設定成非同步, 因為內建的Promise的實體方法then()中的回撥是非同步的
setTimeout(()=>{
callback(onResolved)
})
}
if(this.PromiseState==='rejected'){
// 通過setTimeout將then中的回撥設定成非同步, 因為內建的Promise的實體方法then()中的回撥是非同步的
setTimeout(()=>{
callback(onRejected)
})
}
//
// 考慮executor中非同步任務改變promise範例狀態
if(this.PromiseState==='pendding'){
this.callbacks.push({
onResolved:function(){
callback(onResolved)
},
onRejected:function(){
callback(onRejected)
}
})
}
})
}
// 定義Promise.prototype.catch()
catch(onRejected){
return this.then(undefined,onRejected)
}
// 定義Promise.resolve()靜態方法,注意靜態方法要加關鍵詞static
static resolve(value){
return new Promise((resolve,reject)=>{
if(value instanceof Promise){
value.then(
v=>resolve(v),
e=>reject(e))
}else{
resolve(value)
}
})
}
// 定義Promise.reject()靜態方法
static reject(reason){
return new Promise((resolve,reject)=>{
reject(reason)
})
}
// 定義Promise.all()靜態方法
static all(promises){
let arr=[]
let count=0;
return new Promise((resolve,reject)=>{
for(let i in promises){
promises[i].then(v=>{
arr[i]=v;
count++;
if(count===promises.length){
resolve(arr)
}
},r=>{
reject(r)
})
}
// 為什麼這個判斷不能放在這
// if(count===promises.length){
// resolve(arr)
// }
})
}
// 定義Promise.race()靜態方法
static race(promises){
return new Promise((resolve,reject)=>{
for(let i in promises){
promises[i].then(
v=>resolve(v),
r=>reject(r))
}
})
}
}
async 英文單詞的意思是非同步,雖然它是 ES8 中新增加的一個關鍵字,但它的本質是一種語法糖寫法(語法糖是一種簡化後的程式碼寫法,它能方便程式設計師的程式碼開發),async 通常寫在一個函數的前面,表示這是一個非同步請求的函數,將返回一個 Promise 物件,並可以通過 then 方法取到函數中的返回值
使用 async 關鍵字可以讓函數具有非同步特徵,但總體上其程式碼仍然是同步求值的。而在引數或閉包方面,非同步函數仍然具有普通 JavaScript 函數的正常行為。非同步函數的返回值會被 Promise.resolve()包裝成一個期約物件。非同步函數始終返回期約物件。
ES2017 標準引入了 async 函數,使得非同步操作變得更加方便
async函數可以將非同步操作變為同步操作
async function main(){
// return 521; // Promise<fulfilled>:521
return new Promise((resolve, reject) => {
// resolve('OK'); // Promise<fulfilled>:'OK'
reject('Error'); // Promise<rejected>:'Error'
});
//3. 丟擲異常
// throw "Oh NO"; // Promise<rejected>:"Oh NO"
}
let result = main();
console.log(result);
function print(){
setTimeout(() =>{
console.log("定時器");
},1000)
console.log("Hello");
}
print()
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50);
function ajax(url){
return new Promise(function(resolve,reject){
$.getJSON(url,function(result){
resolve(result)
},function(error){
reject(error)
})
})
}
async function getInfo(){
let ids = await ajax("http://iwenwiki.com/api/generator/list.php")
let names = await ajax("http://iwenwiki.com/api/generator/id.php?id="+ids[0])
let infos = await ajax("http://iwenwiki.com/api/generator/name.php?name=" + names.name)
console.log(infos);
}
getInfo();
實時效果反饋
1. Async
是什麼:
A Async是完成網路請求,如Ajax一樣
B Async的作用是完成非同步網路請求,如Ajax一樣
C Async使得非同步操作變得更加方便
D Async是新的網路請求解決方案
答案
1=>C
await 可以理解為 async wait 的簡寫,表示等待非同步執行完成。await 後可以返回任意的表示式,如果是正常內容,則直接執行,如果是非同步請求,必須等待請求完成後,才會執行下面的程式碼
注意
// 函數 p 返回的是一個 Promise 物件,在物件中,延時 2 秒,執行成功回撥函數,相當於模擬一次非同步請求
function p(v) {
return new Promise(function (resolve) {
setTimeout(function () {
// 在 p 函數執行時,將函數的實參值 v ,作為執行成功回撥函數的返回值。
resolve(v);
}, 2000);
});
}
// 一個用於正常輸出內容的函數
function log() {
console.log("2.正在操作");
}
async function fn() {
console.log("1.開始");
await log();
let p1 = await p("3.非同步請求");
console.log(p1);
console.log("4.結束");
}
fn();
根據頁面效果,原始碼解析如下:
async function main(){
let p = new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
})
//1. 右側為promise的情況
// let res = await p;
//2. 右側為其他型別的資料, 但這種情況不常見
// let res2 = await 20;
//3. 如果promise是失敗的狀態,需要捕獲錯誤來檢視錯誤理由
try{
let res3 = await p;
console.log(res3);
}catch(e){
console.log(e);
}
}
main();
1.txt
觀書有感
-朱熹
半畝方塘一鑑開
天光雲影共徘徊。
2.txt
問渠那得清如許?
為有源頭活水來。
3.txt
---------
中華古詩詞
const fs = require('fs')
const util= require('util')
// 將fs.readFile promise化,即將非同步函數轉換成同步的形式(只是轉換形式,實質還是非同步)
const myReadFile=util.promisify(fs.readFile)
// 傳統的讀多個檔案,並將檔案內容串聯起來輸出。缺點是會有多個回撥巢狀
// fs.readFile('./resource/1.txt',(err,data1)=>{
// if(err) throw err;
// fs.readFile('./resource/2.txt',(err,data2)=>{
// if(err) throw err;
// fs.readFile('./resource/2.txt',(err,data3)=>{
// if(err) throw err;
// console.log(data1+data2+data3);
// })
// })
// })
// 結合使用async和await, 將非同步任務以同步的形式呼叫,減少巢狀,結構更清晰
async function main(){
try{
const data1=await myReadFile('./resource/1.txt')
const data2=await myReadFile('./resource/2.txt')
const data3=await myReadFile('./resource/3.txt')
console.log(data1+data2+data3);
}catch(e){
console.log(e);
}
}
main()
//axios是基於Promise封裝的AJAX請求庫
function sendAJAX(url){
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.open("GET", url);
xhr.send();
//處理結果
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
//判斷成功
if(xhr.status >= 200 && xhr.status < 300){
//成功的結果
resolve(xhr.response);
}else{
reject(xhr.status);
}
}
}
});
}
//段子介面地址 https://api.apiopen.top/getJoke
let btn = document.querySelector('#btn');
btn.addEventListener('click',async function(){
//獲取段子資訊
let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');
console.log(duanzi);
});