@(同步與異步)[callback|Promise|Generator + Co|Async + Await]javascript
同步順序且連續執行,必須執行完畢或返回後纔會繼續執行後續代碼。html
例:
判斷內容數據類型是否爲指望類型值java
/** * 判斷內容值是否爲指望的類型 * @param {*} content 內容值 * @param {String} expect 指望值的類型 * @returns {Boolean} 返回內容值是否爲指望的類型 */ function isType(content, expect) { // 四種判斷類型方法: constructor、typeof、instanceof、Object.prototype.toString let type = Object.prototype.toString.call(content).replace(/\[object\s|\]/g, ''); return type === expect; } console.log(isType('hello', 'String')); // true
擴展:利用高階函數來定義判斷各數據類型的方法ios
/** * 返回用於判斷內容值是否爲指望的類型的函數 * @param {String} expect 指望類型 * @returns {function(*)} 返回判斷函數 */ function isType(expect) { return function (content) { let type = Object.prototype.toString.call(content).replace(/\[object\s|\]/g, ''); return type === expect; } } let util = {}; let types = ['Object', 'Array', 'Function', 'String', 'Number', 'Boolean', 'Null', 'Undefined']; types.forEach((item) => { util[`is${item}`] = isType(item); }); console.log(util.isNumber(86)); // true console.log(util.isBoolean('true')); // false
異步表示非連續執行,如:setTimeout
、Ajax
、事件等方法,一般會在另外一個線程中執行,不會阻塞主線程。 es6
異步編程的方法,大概有下面四種。編程
異步方法若是出錯了不能捕獲try/catch
錯誤
獲取的結果不能經過return
返回
阮一峯:所謂回調函數,就是把任務的第二段單獨寫在一個函數裏面,等到從新執行這個任務的時候,就直接調用這個函數。它的英語名字 callback,直譯過來就是"從新調用"。(原文)數組
雖然回調函數多用於異步編程,但帶有回調函數的方法不必定是異步的。promise
問題1:
第二個請求是依賴於第一個請求
例:瀏覽器
// error-first // 回調方法執行以前拋出的錯誤,程序沒法捕捉,只能看成參數傳入回調方法 fs.readFile('./1.txt', 'utf8', function (err, a) { fs.readFile('./2.txt', 'utf8', function (err, b) { console.log(a, b); }); });
問題2:
兩個異步請求,同時拿到兩個異步請求的結果
例:異步
function after(times, callback) { let arr = []; return function (data) { arr.push(data); if(--times === 0){ callback(arr); } }; } let out = after(2, function (data) { console.log(data); }); out('once'); out('twice');
第二次執行時會一塊兒獲得結果 ['once', 'twice']
。
能夠理解爲回調方法至關於兩個異步請求有關係。
回調函數可能會出現會出現多重嵌套,從而形成代碼難以管理,提升了額外的維護成本,這種狀況被稱爲 產生回調地獄 (Callback Hell)。
let fs = require('fs'); let events = { cbs: [], res: [], on(callback) { this.cbs.push(callback); }, emit(data) { this.res.push(data); this.cbs.forEach(item => item(this.res)); } }; // 訂閱過程 events.on((data) => { if (data.length === 2) { console.log(data); } }); // 訂閱過程 events.on(() => { console.log('good'); }); fs.readFile('file-01.txt', 'utf8', (err, data) => { events.emit(data); }); fs.readFile('file-02.txt', 'utf8', (err, data) => { events.emit(data); });
發佈訂閱同時拿到兩個異步結果,須要回調函數,Promise
不須要回調函數
promise
對象用於表示一個異步操做的最終狀態(完成或失敗),以及其返回的值。
三個狀態 成功態 失敗態 等待態
一個 Promise
有如下幾種狀態:
默認狀態是等待態
等待態能夠變成 成功態或失敗態
成功就不能失敗
也不能從失敗變成成功
不支持的低版本瀏覽器 須要 es6-promise
模塊
Promise
類new Promise
時會傳遞一個執行器 executor
executor
執行器是當即執行的,而且接受兩個函數resolve
和 reject
做爲其參數
每一個 promise
實例都有一個 then
方法,參數是成功和失敗,成功會有成功的值 失敗
同一個 promise
能夠屢次 then
let promise = new Promise((resolve, reject) => { reject('買'); }); promise .then((data) => { console.log('data', data); }, (err) => { console.log('err', err); }); promise .then((data) => { console.log('data', data); }, (err) => { console.log('err', err); });
Promise
解決回調地獄
例:
let fs = require('fs'); function read(path, encoding) { return new Promise((resolve, reject) => { fs.readFile(path, encoding, function (err, data) { if (err) return reject(err); resolve(data); }); }); }
then
同一個 promise
能夠屢次 then
成功的回調或者失敗的回調執行後能夠返回一個 promise
會將這個 promise
的執行結果傳遞給下一次 then
中
若是返回一個普通的值 ,會將這個普通值傳遞倒下一次 then
的成功的參數
read('./file-01.txt', 'utf8') .then(data => { // 由於 read 方法會返回一個 promise ,返回read執行至關於返回一個 promise // 會將這個 promise 的執行成功的結果傳遞給下一次 then 的 resolve return read(data, 'utf8'); }, err => { console.log(err); }) .then(data => { // 若是返回一個普通的值 ,會將這個普通值傳遞倒下一次 then 的成功的參數 return [data]; }, err => { console.log(err); }) .then(data => { // 返回的是一個普通值 console.log(data); // 沒有寫 return ,至關於返回一個 undefined ,下一個then的成功值則爲 undefined }, err => { console.log(err); }) .then(data => { // 上一個 then 沒有返回值,默認值爲 undefined // undefined 也算成功 console.log(data); // 拋出錯誤,將傳給下一個 then 的 reject throw new Error('xxx'); }, err => { console.log(err); }) .then(null, err => { // 若是上一個 then 拋出錯誤,最近的 reject 會執行 // reject 執行後默認下一個 then 會接收 undefined console.log(err); }) .then(data => { // 上一個 then 中失敗沒有返回值,默認爲 undefined console.log('resolve'); }, err => { console.log('reject'); });
reject
能夠將全部 reject
方法都用 catch
代替
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); });
then
穿透let promise = new Promise((resolve, reject) => { resolve('ok); }); // 成功不寫的時候,默認: value => value // 失敗不寫的時候,默認: err => {throw err} promise .then() .then(data => { });
all
解決多個異步請求問題
例:
法等待兩個 promise
都執行完成後,會返回一個新的 promise
若是有一個失敗就失敗了
Promise .all([read('file-01.txt', 'utf8'), read('file-02.txt', 'utf8')]) .then(data => { console.log(data); }, err => { console.log(err); });
race
race
一樣返回一個 Promise
其成功與失敗的狀態取決於最早返回結果的狀態
誰跑的快就用誰的結果
Promise .race([read('file-01.txt', 'utf8'), read('file-02.txt', 'utf8')]) .then(data => { console.log(data); }, err => { console.log(err); });
promise
建立一個一出生就成功或者失敗的 promise
// 成功態 Promise .resolve('123') .then(data => { console.log(data); }); Promise .reject('123') .then(null, err => { console.log(err) });
鏈式調用返回 this
promise
不能返回 this
promise
實現鏈式調用是靠返回一個新的 promise
從 Callback
到了 Promise
時代,不須要再去調用回調函數。Promise
的典型應用 Axios
Fetch
但實際上 Promise
只是對回調函數的改進的寫法而已,並非新的語法功能,原來的語義也被一堆 then
破壞掉了。
es6
實現的 generator
(生成器)會返回一個內部指針(迭代器)。
例:
for (let i of [1, 2, 3]) { console.log(i); }
例:
function arg() { // arguments是一個類數組 // 有索引,有長度,但不是數組 // 沒有數組的方法 // 但能夠被迭代 // 內置iterator for (let i of arguments) { console.log(i); } } arg(1, 2, 3, 4);
例:
let arr = {0: 1, 1: 2, 2: 3, length: 3}; for (let i of arr) { console.log(i); } // TypeError: arr is not iterable // arr是不可被迭代的,由於arr沒有迭代器
爲自定義類數組增長迭代方法,迭代方法是幫咱們迭代
*
表明一個生成器,function
關鍵字和方法名之間有個 *
,這就表明一個 generator
生成器生成一個迭代器,配合 yield
,遇到 yield
就暫停,
迭代器必須返回一個對象,對象裏有一個 next
方法,調用迭代器的 next
方法移動內部指針,每調用一次 next
方法就能夠返回一個對象 {value,done}
,對象裏有倆屬性:表示當前階段的信息 ,value
爲 yield
表達式的值,done
是一個表示是否執行完畢的布爾值。
再次調用 next
繼續執行。
遇到 return
時就迭代完成了,沒寫 return
則爲 return undefined
,也算完成了
function * thing() { // 生成器返回一個迭代器 // yield產出,而且爲未完成狀態 yield 1; // return表示完成 return 2; } // 返回一個迭代器 let it = thing();
迭代器裏有 next
方法,返回一個對象,包含 done
和 value
// { value: 1, done: false } console.log(it.next()); // { value: 2, done: true } console.log(it.next());
傳值
function * some() { let a = yield 1; console.log('a', a); let b = yield 2; console.log('b', b); return b; } let so = some(); // 第一個next傳遞參數無效,沒有意義 // 傳了100也沒用 console.log(so.next('100')); // { value: 1, done: false } // 第二個next傳參是第一次yield的返回值 console.log(so.next('200')); // a 200 // { value: 2, done: false } console.log(so.next('300')); // b 300 // { value: '300', done: true }
例:
獲取 1.txt
內容:2.txt
2.txt
內容是最終結果
let blueBird = require('bluebird'); let fs = require('fs'); let read = blueBird.promisify(fs.readFile); function* readFile() { let data1 = yield read('1.txt', 'utf8'); let data2 = yield read(data1, 'utf8'); return data2; }
promise
使用:let it = readFile(); it.next().value .then(data => { return it.next(data).value; }) .then(data => { console.log(data); });
co
使用:function co(it) { return new Promise((resolve, reject) => { function next(dataset) { let {value, done} = it.next(dataset); if (!done) { value.then(data => { next(data); }, reject); } else { resolve(value); } } next(); }); } co(readFile()) .then(data => { console.log(data); });
co
庫let co = require('co'); co(readFile()) .then(data => { console.log(data); });
async await
實際上是一個語法糖 async
+ await
= generator
+ co
let blueBird = require('bluebird'); let fs = require('fs'); let read = blueBird.promisify(fs.readFile); async function readFile() { let data1 = await read('1.txt', 'utf8'); let data2 = await read(data1, 'utf8'); return data2; } readFile() .then(data => { console.log(data); });
完