- 回調(diào)函數(shù)
假定有兩個函數(shù)f1和f2,后者等待前者的執(zhí)行結(jié)果。
f1(); f2();
如果f1是一個很耗時(shí)的任務(wù),可以考慮改寫為f1,把f2寫為f1的回調(diào)函數(shù)。
執(zhí)行的時(shí)候就變成了f1(f2); 采用這種方法把同步變成異步,f1不會堵塞程序運(yùn)行,相當(dāng)于先執(zhí)行程序的主要邏輯,將耗時(shí)都部分推遲執(zhí)行。function f1(callback){ setTimeout(function(){ //f1代碼 callback(); },1000); }
回調(diào)函數(shù)優(yōu)點(diǎn):簡單、容易理解和部署;缺點(diǎn):不利于代碼的閱讀和維護(hù)。
一個同步(阻塞)中使用回調(diào)的例子,目的是在func1代碼執(zhí)行完成后執(zhí)行func2代碼。var func1 = function(callback){ // do something (callback && typeof(callback) === "function") && callback(); } func1(func2); var func2 = function(){}
更多回調(diào)函數(shù)請參考:
- JavaScript回調(diào)函數(shù)
- 淺談 javascript 回調(diào)函數(shù)
- JavaScript:回調(diào)是什么鬼?
- JS 回調(diào)函數(shù)詳解
- 事件監(jiān)聽
任務(wù)的執(zhí)行不取決于代碼的順序,而取決于某件事件是否發(fā)生。
為f1綁定一個事件:f1.on('done', f2);當(dāng)f1發(fā)生done事件,就執(zhí)行f2。然后對f1進(jìn)行改寫為
優(yōu)點(diǎn):比較容易理解,綁定多個事件,每個事件可以指定多個回調(diào)函數(shù),去耦合,實(shí)現(xiàn)模塊化。function f1(){ setTimeout(function*(){ // f1的任務(wù)代碼 f1.trigger('done'); },1000); } - 發(fā)布/訂閱
例如:jquery中一個插件:Tiny Pub/Sub- f2向信號中心"jquery訂閱"done信號: jquery.subscribe("done", f2);
- f1函數(shù)如下:
jquey.publish('done');就是f1執(zhí)行完畢之后,向信號中心發(fā)送done信號,從而引發(fā)f2的執(zhí)行。f2執(zhí)行后,也可以取消訂閱function f1(){ setTimeout(function(){ // f1的執(zhí)行代碼 jquey.publish('done'); }, 1000); }jquery.unsubscribe("done", f2);
- Promises對象:為異步編程提供統(tǒng)一接口。
思想:每一個異步任務(wù)返回一個Promise對象,該對象有一個then方法,允許指向回調(diào)函數(shù)。
Promise的三種狀態(tài):- Pending:Promise對象實(shí)例創(chuàng)建時(shí)候的初始狀態(tài)。
- Fulfilled:成功的狀態(tài)
- Rejected:失敗的狀態(tài)
Promise ---> resolved ---> then(回調(diào)callback) 或者 ---> rejected ---> catch(回調(diào)callback)。Promise一旦從等待狀態(tài)變成其他狀態(tài)就永遠(yuǎn)不能改變狀態(tài)了。
構(gòu)造函數(shù)內(nèi)部的代碼立刻執(zhí)行。const instance = new Promise((resolve, reject) => { // 一些異步操作 if(/*異步操作成功*/) { resolve(value); } else { reject(error); } } }) instance.then(value => { // do something... }, error => { // do something... })
then 方法會返回一個新的 Promise 實(shí)例,可以分兩種情況來看:- 指定返回值是新的 Promise 對象,如return new Promise(...),這種情況沒啥好說的,由于返回的是 Promise,后面顯然可以繼續(xù)調(diào)用then方法。
- 返回值不是Promise, 如:return 1 這種情況還是會返回一個 Promise,并且這個Promise 立即執(zhí)行回調(diào) resolve(1)。所以仍然可以鏈?zhǔn)秸{(diào)用then方法。(注:如果沒有指定return語句,相當(dāng)于返回了undefined)。
- 示例一
function sayHi(name) { return new Promise((resolve, reject) => { setTimeout(() => { resolve(name); }, 2000) }) } sayHi('張三') .then(name => { console.log(`你好, ${name}`); return sayHi('李四'); // 最終 resolved 函數(shù)中的參數(shù)將作為值傳遞給下一個then }) // name 是上一個then傳遞出來的參數(shù) .then(name => { console.log(`你好, ${name}`); return sayHi('王二麻子'); }) .then(name => { console.log(`你好, ${name}`); }) // 你好, 張三 // 你好, 李四 // 你好, 王二麻子- 示例二
read('./file-01.txt', 'utf8') .then(data => { // 因?yàn)?read 方法會返回一個 promise ,返回read執(zhí)行相當(dāng)于返回一個 promise // 會將這個 promise 的執(zhí)行成功的結(jié)果傳遞給下一次 then 的 resolve return read(data, 'utf8'); }, err => { console.log(err); }) .then(data => { // 如果返回一個普通的值 ,會將這個普通值傳遞倒下一次 then 的成功的參數(shù) return [data]; }, err => { console.log(err); }) .then(data => { // 返回的是一個普通值 console.log(data); // 沒有寫 return ,相當(dāng)于返回一個 undefined ,下一個then的成功值則為 undefined }, err => { console.log(err); }) .then(data => { // 上一個 then 沒有返回值,默認(rèn)值為 undefined // undefined 也算成功 console.log(data); // 拋出錯誤,將傳給下一個 then 的 reject throw new Error('xxx'); }, err => { console.log(err); }) .then(null, err => { // 如果上一個 then 拋出錯誤,最近的 reject 會執(zhí)行 // reject 執(zhí)行后默認(rèn)下一個 then 會接收 undefined console.log(err); }) .then(data => { // 上一個 then 中失敗沒有返回值,默認(rèn)為 undefined console.log('resolve'); }, err => { console.log('reject'); });- 示例三:使用catch代替err
read('./file-01.txt', 'utf8') .then(data => { return read(data, 'utf8'); }) .then(data => { return [data]; }) .then(data => { console.log(data); }) .then(data => { console.log(data); // 拋出錯誤后,找到最近的接收錯誤方法 // 如果所有的 then 都沒有 reject 方法,則找最后一個 catch throw new Error('xxx'); }) .then(null) .then(data => { console.log('resolve'); }) .catch(err => { console.log(err); });
- async/await
- async/await是基于Promise實(shí)現(xiàn)的,不能用于普通的回調(diào)函數(shù)。
- 和Promise一樣,是非阻塞的。
- 使異步代碼看起來像是同步代碼。
- 一個函數(shù)如果加上async,那么該函數(shù)就會返回一個Promise,(如果指定的返回值不是Promise對象,也返回一個Promise,只不過立即 resolve ,處理方式同 then 方法,因此 async 函數(shù)通過 return 返回的值,會成為 then 方法中回調(diào)函數(shù)的參數(shù)。單獨(dú)一個 async 函數(shù),其實(shí)與Promise執(zhí)行的功能是一樣的。
- await 就是異步等待,它等待的是一個Promise,因此 await 后面應(yīng)該寫一個Promise對象,如果不是Promise對象,那么會被轉(zhuǎn)成一個立即 resolve 的Promise
- 實(shí)例一
let fs = require('fs') function read(file) { return new Promise(function(resolve, reject) { fs.readFile(file, 'utf8', function(err, data) { if (err) reject(err) resolve(data) }) }) } async function readResult(params) { try { let p1 = await read(params, 'utf8') //await后面跟的是一個Promise實(shí)例, //await返回的結(jié)果直接return的結(jié)果,不需要進(jìn)行新的then操作。 let p2 = await read(p1, 'utf8') let p3 = await read(p2, 'utf8') console.log('p1', p1) console.log('p2', p2) console.log('p3', p3) return p3 } catch (error) { console.log(error) } } readResult('1.txt').then( // async函數(shù)返回的也是個promise data => { console.log(data) }, err => console.log(err) ) // p1 2.txt // p2 3.txt // p3 結(jié)束 // 結(jié)束 - 示例二
function readAll() { read1() read2()//這個函數(shù)同步執(zhí)行 } async function read1() { let r = await read('1.txt','utf8') console.log(r) } async function read2() { let r = await read('2.txt','utf8') console.log(r) } readAll() // 2.txt 3.txt- 示例三
async function func() { try { const num1 = await 200; console.log(`num1 is ${num1}`); const num2 = await Promise.reject('num2 is wrong!'); console.log(`num2 is ${num2}`); const num3 = await num2 + 100; console.log(`num3 is ${num3}`); } catch (error) { console.log(error); } } func(); // num1 is 200 // 出錯了 // num2 is wrong!
參考:
- Javascript異步編程的4種方法
- 異步方法的發(fā)展流程
- JS 異步編程六種方案