Study Notes
本博主會持續(xù)更新各種前端的技術(shù),如果各位道友喜歡,可以關(guān)注、收藏、點贊下本博主的文章。
函數(shù)是一等公民(First-class Function)
特性
可將函數(shù)賦值給變量,即函數(shù)可存儲在變量中
函數(shù)可作為參數(shù)
函數(shù)可作為返回值
說明
在 JavaScript 中函數(shù)就是一個普通的對象 (可以通過 new Function() ),我們可以把函數(shù)存儲到變量/數(shù)組中,它還可以作為另一個函數(shù)的參數(shù)和返回值,甚至我們可以在程序運行的時候通過 new Function('alert(1)') 來構(gòu)造一個新的函數(shù)。
demo
// 可將函數(shù)賦值給變量,即函數(shù)可存儲在變量中
const foo = function () {
console.log('foobar');
};
// 用變量來調(diào)用它
foo();
// 函數(shù)可作為參數(shù)
function sayHello() {
return 'Hello, ';
}
function greeting(helloMessage, name) {
console.log(helloMessage() + name);
}
// 傳遞 `sayHello` 作為 `greeting` 函數(shù)的參數(shù)
greeting(sayHello, 'JavaScript!'); // Hello, JavaScript!
// 函數(shù)可作為返回值
function sayHello() {
return function () {
console.log('Hello!');
};
}
高階函數(shù)(Higher-order function)
特性
可以把函數(shù)作為參數(shù)傳遞給另一個函數(shù)
可以把函數(shù)作為另一個函數(shù)的返回結(jié)果
使用高階函數(shù)的意義
抽象可以幫我們屏蔽細節(jié),只需要關(guān)注我們的目標
高階函數(shù)是用來抽象通用的問題
demo
可以把函數(shù)作為參數(shù)傳遞給另一個函數(shù)
/**
* forEach 遍歷數(shù)組
* @param array {Array} 所需遍歷的數(shù)組
* @param fn {Function} 返回值
*/
const forEach = (array, fn) => {
for (let val of array) {
fn(val);
}
};
// 測試
let array = [1, 343, 5, 7, 8345, 8];
forEach(array, (val) => console.log(val));
/* 輸出
* 1
* 343
* 5
* 7
* 8345
* 8
* */
/**
* filter 數(shù)組過濾,并返回新的數(shù)組
* @param array {Array} 所需過濾的數(shù)組
* @param fn {Function} 過濾處理函數(shù)
* @returns {Array} 返回值
*/
const filter = (array, fn) => {
let result = [];
for (let val of array) {
if (fn(val)) {
result.push(val);
}
}
return result;
};
// 測試
let result = filter(array, (val) => val > 100);
console.log('filter:', result);
/* 輸出
* filter: [ 343, 8345 ]
* */
可以把函數(shù)作為參數(shù)傳遞給另一個函數(shù)
/**
* makeFn 函數(shù)生成
* @returns {function(): void}
*/
const makeFn = () => {
const msg = 'Hello World';
return () => console.log(msg);
};
// 測試
//調(diào)用方式一
const fn = makeFn();
fn();
// 調(diào)用方式二
// makeFn()();
/* 輸出
* Hello World
* */
/**
* once 只執(zhí)行一次
* @param fn {Function} 執(zhí)行函數(shù)
* @returns {Function} 返回值
*/
const once = (fn) => {
let done = false;
// 因為下面使用this,這里切不可使用箭頭函數(shù),箭頭函數(shù)里this的指向是上下文里對象this指向,如果沒有上下文對象,this則指向window
return function () {
if (!done) {
done = true;
return fn.apply(this, arguments);
}
};
};
//測試
const pay = once((money) => {
console.log(`支付:¥${money}`);
});
pay(100);
pay(100);
pay(100);
// 這里調(diào)用了三次函數(shù),但是結(jié)果只輸出一次,所以我們的once達到了預(yù)期效果
/* 輸出
* 支付:¥100
* */
模擬常用高階函數(shù) map、every、some
/**
* map 遍歷數(shù)組,對其進行處理并返回新的數(shù)組
* @param array {Array} 所需遍歷數(shù)組
* @param fn {Function} 處理函數(shù)
* @returns {Array} 返回值
*/
const map = (array, fn) => {
let result = [];
for (let val of array) {
result.push(fn(val));
}
return result;
};
// 測試
let newArr = map(array, (val) => val * val);
console.log(newArr);
/* 輸出
* [ 1, 117649, 25, 49, 69639025, 64 ]
* */
/**
* every 遍歷數(shù)組,判斷數(shù)組所有元素是否全部滿足指定條件,并返回結(jié)果
* @param array {Array} 所需遍歷的數(shù)組
* @param fn {Function} 指定條件函數(shù)
* @returns {boolean} 返回值
*/
const every = (array, fn) => {
let result = true;
for (let val of array) {
result = fn(val);
if (!result) {
break;
}
}
return result;
};
// 測試
let result1 = every(array, (val) => val > 0);
let result2 = every(array, (val) => val > 1);
console.log(result1);
console.log(result2);
/* 輸出
* true
* false
* */
/**
* some 遍歷數(shù)組,判斷數(shù)組所有元素是否有滿足指定條件的元素,并返回結(jié)果
* @param array {Array} 所需遍歷的數(shù)組
* @param fn {Function} 指定條件函數(shù)
* @returns {boolean} 返回值
*/
const some = (array, fn) => {
let result = false;
for (let val of array) {
result = fn(val);
if (result) {
break;
}
}
return result;
};
// 測試
let result3 = some(array, (val) => val > 0);
let result4 = some(array, (val) => val > 10000);
console.log(result3);
console.log(result4);
/* 輸出
* true
* false
* */
閉包(Closure)
描述
閉包 (Closure):函數(shù)和其周圍的狀態(tài)(詞法環(huán)境)的引用捆綁在一起形成閉包。
可以在另一個作用域中調(diào)用一個函數(shù)的內(nèi)部函數(shù)并訪問到該函數(shù)的作用域中的成員
閉包的本質(zhì):函數(shù)在執(zhí)行的時候會放到一個執(zhí)行棧上當函數(shù)執(zhí)行完畢之后會從執(zhí)行棧上移除,但是堆上的作用域成員因為被外部引用不能釋放,因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)的成員
瀏覽器調(diào)試工具使用
Call Stack 函數(shù)調(diào)用棧(在匿名函數(shù)中調(diào)用) 一個函數(shù)執(zhí)行后,會從函數(shù)調(diào)用棧中移除
Scope 作用域
demo
/**
* makePower 生成冪數(shù)函數(shù)
* @param power {Number} n次方
* @returns {function(*=): number} 返回值
*/
const makePower = (power) => {
return (number) => {
return Math.pow(number, power);
};
};
// 測試
const power2 = makePower(2);
const power3 = makePower(3);
console.log(power2(2));
console.log(power2(3));
console.log(power3(2));
/**
* 輸出
* 4
* 9
* 8
*/
/**
* makeSalary 工資生成器
* @param base {Number} 基本工資
* @returns {function(*): *}
*/
const makeSalary = (base) => {
return (performance) => {
return base + performance;
};
};
// 測試
const getSalaryLevel1 = makeSalary(10000);
const getSalaryLevel2 = makeSalary(12000);
console.log(getSalaryLevel1(2000));
console.log(getSalaryLevel1(3000));
console.log(getSalaryLevel2(2000));
/**
* 輸出
* 12000
* 13000
* 14000
*/
純函數(shù)(Pure Functions)
純函數(shù)概念
純函數(shù):相同的輸入永遠會得到相同的輸出,而且沒有任何可觀察的副作用
- 純函數(shù)就類似數(shù)學(xué)中的函數(shù)(用來描述輸入和輸出之間的關(guān)系),y = f(x)
lodash 是一個一致性、模塊化、高性能的 JavaScript 實用工具庫(lodash 的 fp 模塊提供了對函數(shù)式編程友好的方法),提供了對數(shù)組、數(shù)字、對象、字符串、函數(shù)等操作的一些方法
數(shù)組的 slice 和 splice 分別是:純函數(shù)和不純的函數(shù)
- slice 返回數(shù)組中的指定部分,不會改變原數(shù)組
- splice 對數(shù)組進行操作返回該數(shù)組,會改變原數(shù)組
// 純函數(shù)
let array = [1, 2, 3, 4, 5];
console.log('slice: ', array.slice(0, 3));
console.log('slice: ', array.slice(0, 3));
console.log('slice: ', array.slice(0, 3));
// 不純的函數(shù)
console.log('splice: ', array.splice(0, 3));
console.log('splice: ', array.splice(0, 3));
console.log('splice: ', array.splice(0, 3));
// slice: [ 1, 2, 3 ]
// slice: [ 1, 2, 3 ]
// slice: [ 1, 2, 3 ]
// splice: [ 1, 2, 3 ]
// splice: [ 4, 5 ]
// splice: []
函數(shù)式編程不會保留計算中間的結(jié)果,所以變量是不可變的(無狀態(tài)的)
我們可以把一個函數(shù)的執(zhí)行結(jié)果交給另一個函數(shù)去處理
純函數(shù)的好處
可緩存
- 因為純函數(shù)對相同的輸入始終有相同的結(jié)果,所以可以把純函數(shù)的結(jié)果緩存起來
const _ = require('lodash');
const add = (a, b) => {
console.log(a, b);
return a + b;
};
const result = _.memoize(add);
console.log(result(1, 2));
console.log(result(1, 2));
console.log(result(1, 2));
const memoize = (fn) => {
let cache = {};
// 箭頭函數(shù)沒有arguments,因為下面使用了arguments,所以這里不能使用箭頭函數(shù)
return function () {
let key = JSON.stringify(arguments);
cache[key] = cache[key] || fn.apply(fn, arguments);
return cache[key];
};
};
const result1 = memoize(add);
console.log(result1(1, 2));
console.log(result1(1, 2));
console.log(result1(1, 2));
// 輸出結(jié)果 從結(jié)果可以看出來,參數(shù)只被打印一次,說明函數(shù)緩存成功
// 1 2
// 3
// 3
// 3
// 1 2
// 3
// 3
// 3
可測試
- 純函數(shù)讓測試更方便
并行處理
- 在多線程環(huán)境下并行操作共享的內(nèi)存數(shù)據(jù)很可能會出現(xiàn)意外情況
- 純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù),所以在并行環(huán)境下可以任意運行純函數(shù) (Web Worker)
副作用
// 不純的
let mini = 18;
function checkAge(age) {
return age >= mini;
}
// 純的(有硬編碼,后續(xù)可以通過柯里化解決)
function checkAge(age) {
let mini = 18;
return age >= mini;
}
副作用讓一個函數(shù)變的不純(如上例),純函數(shù)的根據(jù)相同的輸入返回相同的輸出,如果函數(shù)依賴于外部的狀態(tài)就無法保證輸出相同,就會帶來副作用。
副作用來源:
- 配置文件
- 數(shù)據(jù)庫
- 獲取用戶的輸入
- ……
所有的外部交互都有可能帶來副作用,副作用也使得方法通用性下降不適合擴展和可重用性,同時副作用會給程序中帶來安全隱患給程序帶來不確定性,但是副作用不可能完全禁止,盡可能控制它們在可控范圍內(nèi)發(fā)生。
柯里化 (Haskell Brooks Curry)
柯里化 (Currying):
- 當一個函數(shù)有多個參數(shù)的時候先傳遞一部分的參數(shù)調(diào)用它(這部分參數(shù)以后永遠不變)
- 然后返回一個新的函數(shù)來接收剩余的參數(shù),返回結(jié)果
lodash 中的柯里化函數(shù)
_.curry(func)
- 功能:創(chuàng)建一個函數(shù),該函數(shù)接收一個或多個 func 的參數(shù),如果 func 所需要的參數(shù)都被提供則執(zhí)行 func 并返回執(zhí)行的結(jié)果。否則繼續(xù)返回該函數(shù)并等待接收剩余的參數(shù)。
- 參數(shù):需要柯里化的函數(shù)
- 返回值:柯里化后的函數(shù)
// lodash 中的柯里化函數(shù)
const _ = require('lodash');
const getSum = (a, b, c) => {
return a + b + c;
};
const curried = _.curry(getSum);
console.log(curried(1, 2, 3));
console.log(curried(1)(2, 3));
console.log(curried(1, 2)(3));
/**
* 輸出結(jié)果
* 6
* 6
* 6
*/
// 案例
const match = _.curry((res, str) => {
return str.match(res);
});
const haveSpace = match(/\s+/g);
const haveNumber = match(/\d+/g);
const filter = _.curry((fun, array) => {
return array.filter(fun);
});
const findSpace = filter(haveSpace);
const findNumber = filter(haveNumber);
console.log(haveSpace('ss ss'));
console.log(haveNumber('ss 12'));
console.log(findSpace(['ss12', 's ss']));
console.log(findNumber(['ss12', 'sss']));
/**
* 輸出結(jié)果
* [ ' ' ]
* [ '12' ]
* [ 's ss' ]
* [ 'ss12' ]
*/
模擬 _.curry() 的實現(xiàn)
// 模擬 curry
const getSum = (a, b, c) => {
return a + b + c;
};
const curry = (func) => {
return (curryFn = (...args) => {
if (args.length < func.length) {
return function () {
return curryFn(...args.concat(Array.from(arguments)));
};
}
return func(...args);
});
};
const curried = curry(getSum);
console.log(curried(1, 2, 3));
console.log(curried(1)(2, 3));
console.log(curried(1, 2)(3));
/**
* 輸出結(jié)果
* 6
* 6
* 6
*/
總結(jié)
柯里化可以讓我們給一個函數(shù)傳遞較少的參數(shù)得到一個已經(jīng)記住了某些固定參數(shù)的新函數(shù)
這是一種對函數(shù)參數(shù)的'緩存'
讓函數(shù)變的更靈活,讓函數(shù)的粒度更小
可以把多元函數(shù)轉(zhuǎn)換成一元函數(shù),可以組合使用函數(shù)產(chǎn)生強大的功能
函數(shù)組合(compose)
純函數(shù)和柯里化很容易寫出洋蔥代碼 h(g(f(x)))
函數(shù)組合可以讓我們把細粒度的函數(shù)重新組合生成一個新的函數(shù)
- 獲取數(shù)組的最后一個元素再轉(zhuǎn)換成大寫字母, .toUpper(.first(_.reverse(array)))
函數(shù)組合
函數(shù)組合 (compose):如果一個函數(shù)要經(jīng)過多個函數(shù)處理才能得到最終值,這個時候可以把中間過程的函數(shù)合并成一個函數(shù)
函數(shù)就像是數(shù)據(jù)的管道,函數(shù)組合就是把這些管道連接起來,讓數(shù)據(jù)穿過多個管道形成最終結(jié)果
函數(shù)組合默認是從右到左執(zhí)行
lodash 中的組合函數(shù)
- lodash 中組合函數(shù) flow() 或者 flowRight(),他們都可以組合多個函數(shù)
- flow() 是從左到右運行
- flowRight() 是從右到左運行,使用的更多一些
const _ = require('lodash');
// lodash 中組合函數(shù) flowRight(),將數(shù)組的最后一個元素轉(zhuǎn)換為大寫
/**
* first 獲取字符串第一個字符或數(shù)組第一個元素
* @param str {String || Array} 字符串或數(shù)組
* @returns {*} 返回值
*/
const first = (str) => {
return _.first(str);
};
/**
* reverse 顛倒字符串
* @param str {String} 字符串
* @param isReverse {Boolean} 是否顛倒
* @returns {*} 返回值
*/
const reverse = (str, isReverse) => {
let array = str.split('');
array = isReverse ? array.reverse() : array;
return array;
};
/**
* toUpperCase 將字母轉(zhuǎn)換為大寫
* @param str {String} 字符串
* @returns {string} 返回值
*/
const toUpperCase = (str) => {
return str.toUpperCase();
};
const fn = _.flowRight(toUpperCase, first, reverse);
console.log(fn('abc', true));
/**
* 輸出結(jié)果
* C
*/
模擬實現(xiàn) lodash 的 flowRight 方法
// 模擬 flowRight
/**
* compose 函數(shù)組合
* @param args 參數(shù)
* @returns {function(...[*]=): *}
*/
const compose = (...args) => {
let first = false;
return (...args1) => {
return args.reverse().reduce((val, fn) => {
if (!first) {
first = true;
return fn(...val);
}
return fn(val);
}, args1);
};
};
const fn = compose(toUpperCase, first, reverse);
console.log(fn('abc', false));
/**
* 輸出結(jié)果
* A
*/
函數(shù)的組合要滿足結(jié)合律 (associativity):
- 我們既可以把 g 和 h 組合,還可以把 f 和 g 組合,結(jié)果都是一樣的
// 滿足結(jié)合律
const fn1 = compose(toUpperCase, first, reverse);
const fn2 = compose(compose(toUpperCase, first), reverse);
const fn3 = compose(toUpperCase, compose(first, reverse));
console.log(fn1('abc', true));
console.log(fn2('abc', true));
console.log(fn3('abc', true));
/**
* 輸出結(jié)果
* C
* C
* C
*/
lodash 的 fp 模塊提供了實用的對函數(shù)式編程友好的方法
提供了不可變 auto-curried iteratee-first data-last 的方法
Point Free
Point Free:我們可以把數(shù)據(jù)處理的過程定義成與數(shù)據(jù)無關(guān)的合成運算,不需要用到代表數(shù)據(jù)的那個參數(shù),只要把簡單的運算步驟合成到一起,在使用這種模式之前我們需要定義一些輔助的基本運算函數(shù)。
不需要指明處理的數(shù)據(jù)
只需要合成運算過程
需要定義一些輔助的基本運算函數(shù)
demo
/// 非 Point Free 模式
// Hello World => hello_world
function f(word) {
return word.toLowerCase().replace(/\s+/g, '_');
}
console.log(f('Hello World'));
// Point Free
const fp = require('lodash/fp');
const firstLetterToUpper = fp.flowRight(
fp.join('. '),
fp.map(fp.flowRight(fp.first, fp.toUpper)),
fp.split(' '),
);
console.log(firstLetterToUpper('world wild web'));
// => W. W. W
函子(Functor)
概念
容器:包含值和值的變形關(guān)系(這個變形關(guān)系就是函數(shù))
函子:是一個特殊的容器,通過一個普通的對象來實現(xiàn),該對象具有 map 方法,map 方法可以運行一個函數(shù)對值進行處理(變形關(guān)系)
總結(jié)
函數(shù)式編程的運算不直接操作值,而是由函子完成
函子就是一個實現(xiàn)了 map 契約的對象
我們可以把函子想象成一個盒子,這個盒子里封裝了一個值
想要處理盒子中的值,我們需要給盒子的 map 方法傳遞一個處理值的函數(shù)(純函數(shù)),由這個函數(shù)來對值進行處理
最終 map 方法返回一個包含新值的盒子(函子)
demo
// Functor
class Container {
/**
* constructor 構(gòu)造函數(shù)
* @param value 入?yún)? */
constructor(value) {
this._value = value;
}
/**
* of 實例化
* @param value 入?yún)? * @returns {Container} 返回Container對象
*/
static of(value) {
return new Container(value);
}
/**
* map 方法
* @param fn {function} 處理函數(shù)
* @returns {Container} 返回Container對象
*/
map(fn) {
return Container.of(fn(this._value));
}
}
let r = Container.of(5)
.map((v) => v * v)
.map((v) => v + 1);
console.log(r);
/**
* 輸出結(jié)果
* Container { _value: 26 }
*/
MayBe 函子
我們在編程的過程中可能會遇到很多錯誤,需要對這些錯誤做相應(yīng)的處理
MayBe 函子的作用就是可以對外部的空值情況做處理(控制副作用在允許的范圍)
demo
// MayBe 函子
class MayBe {
/**
* constructor 構(gòu)造函數(shù)
* @param value 入?yún)? */
constructor(value) {
this._value = value;
}
/**
* of 實例化
* @param value 入?yún)? * @returns {MayBe} 返回MayBe對象
*/
static of(value) {
return new MayBe(value);
}
/**
* map 方法
* @param fn {function} 處理函數(shù)
* @returns {MayBe} 返回MayBe對象
*/
map(fn) {
return this.isNothing() ? MayBe.of(this._value) : MayBe.of(fn(this._value));
}
/**
* isNothing 判斷是否為null或undefined
* @returns {boolean}
*/
isNothing() {
return this._value === null || this._value === undefined;
}
}
let r = MayBe.of('abc')
.map((val) => val.toUpperCase())
.map((val) => val.split(''));
let r1 = MayBe.of(null)
.map((val) => val.toUpperCase())
.map((val) => val.split(''));
let r2 = MayBe.of(undefined)
.map((val) => val.toUpperCase())
.map((val) => val.split(''));
console.log(r);
console.log(r1);
console.log(r2);
/**
* 輸出結(jié)果
* MayBe { _value: [ 'A', 'B', 'C' ] }
* MayBe { _value: null }
* MayBe { _value: undefined }
*/
Either 函子
Either 兩者中的任何一個,類似于 if...else...的處理
異常會讓函數(shù)變的不純,Either 函子可以用來做異常處理
demo
// Either 函子
class Either {
/**
* constructor 構(gòu)造函數(shù)
* @param value 入?yún)? */
constructor(value) {
this._value = value;
}
/**
* of 實例化
* @param value 入?yún)? * @returns {Either} 返回Either對象
*/
static of(value) {
return new Either(value);
}
/**
* map 方法
* @param fn {function} 處理函數(shù)
* @returns {Either} 返回Either對象
*/
map(fn) {
return Either.of(fn(this._value));
}
}
class Error extends Either {
/**
*
* @param fn
* @returns {Error} 返回Error對象的this
*/
map(fn) {
return this;
}
}
/**
* parseJson 解析字符串JSON
* @param str {string} 入?yún)? * @returns {Either} 返回Either對象
*/
const parseJson = (str) => {
try {
return Either.of(JSON.parse(str));
} catch (e) {
return Error.of({ error: e.message });
}
};
const r = parseJson('{"name": "zs"}');
const r1 = parseJson('{name: "zs"}');
console.log(r.map((val) => val.name.toUpperCase())); // 正常
console.log(r1); // 異常
/**
* 輸出結(jié)果
* Either { _value: 'ZS' }
* Either { _value: { error: 'Unexpected token n in JSON at position 1' }}
*/
IO 函子
IO 函子中的 _value 是一個函數(shù),這里是把函數(shù)作為值來處理
IO 函子可以把不純的動作存儲到 _value 中,延遲執(zhí)行這個不純的操作(惰性執(zhí)行),包裝當前的操作純
把不純的操作交給調(diào)用者來處理
demo
// IO 函子
const _ = require('lodash');
class IO {
/**
* 構(gòu)造函數(shù)
* @param fn {function} 函數(shù)
*/
constructor(fn) {
this._value = fn;
}
/**
* 實例化
* @param value
* @returns {IO} IO對象
*/
static of(value) {
return new IO(function () {
return value;
});
}
/**
* map 方法
* @param fn {function} 處理函數(shù)
* @returns {IO} 返回IO對象
*/
map(fn) {
return new IO(_.flowRight(fn, this._value));
}
}
const r = IO.of(process).map((p) => p.execPath);
console.log(r._value());
/**
* 輸出結(jié)果
* D:\Development\nodeJS\node.exe
*/
IO 函子的問題
- 嵌套函子時,需要多次獲取 value
/**
* 讀取文件內(nèi)容
* @param filename {string} 文件名
* @returns {IO} 返回IO對象
*/
const readFile = (filename) => {
return new IO(() => {
return fs.readFileSync(filename, 'utf-8');
});
};
/**
* 打印
* @param value {string} 入?yún)? * @returns {IO} 返回IO對象
*/
const print = (value) => {
return new IO(() => {
return value;
});
};
/**
* 讀取文件并打印
*/
const cat = _.flowRight(print, readFile);
// 因為這里是嵌套函子,所以這邊打印出來的是IO(IO())
console.log(cat('package.json'));
// 所以需要連續(xù)兩次獲取value才能取到值
console.log(cat('package.json')._value()._value());
Task 異步執(zhí)行
folktale 一個標準的函數(shù)式編程庫
// Task 異步執(zhí)行
const { task } = require('folktale/concurrency/task');
const fs = require('fs');
/**
* 讀取文件內(nèi)容
* @param filename {String} 文件名
* @returns {*} 返回task對象
*/
const readFile = (filename) => {
return task((resolve) => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) resolve.reject(err);
resolve.resolve(data);
});
});
};
readFile('package.json')
.map((v) => JSON.parse(v))
.run()
.listen({
onRejected: (err) => {
console.log('異常', err);
},
onResolved: (data) => {
console.log(data.version);
},
});
/**
* 輸出結(jié)果
* 1.0.0
*/
Monad 函子
Monad 函子是可以變扁的 Pointed 函子,IO(IO(x))
一個函子如果具有 join 和 of 兩個方法并遵守一些定律就是一個 Monad
// Monad 函子
const _ = require('lodash');
const fs = require('fs');
class IO {
/**
* 構(gòu)造函數(shù)
* @param fn {function} 函數(shù)
*/
constructor(fn) {
this._value = fn;
}
/**
* 實例化
* @param value
* @returns {IO} IO對象
*/
static of(value) {
return new IO(function () {
return value;
});
}
/**
* map 方法
* @param fn {function} 處理函數(shù)
* @returns {IO} 返回IO對象
*/
map(fn) {
return new IO(_.flowRight(fn, this._value));
}
join() {
return this._value();
}
flatMap(fn) {
return this.map(fn).join();
}
}
/**
* 讀取文件內(nèi)容
* @param filename {string} 文件名
* @returns {IO} 返回IO對象
*/
const readFile = (filename) => {
return new IO(() => {
return fs.readFileSync(filename, 'utf-8');
});
};
/**
* 打印
* @param value {string} 入?yún)? * @returns {IO} 返回IO對象
*/
const print = (value) => {
return new IO(() => {
return value;
});
};
/**
* 讀取文件并打印
*/
const cat = readFile('package.json')
.map((v) => JSON.parse(v))
.map((v) => v.version)
.flatMap(print)
.join();
console.log(cat);
/**
* 輸出結(jié)果
* 1.0.0
*/
異步模式
異步任務(wù)
異步任務(wù)是指不進入主線程,而進入任務(wù)隊列的任務(wù),只有任務(wù)隊列通知主線程,某個異步任務(wù)可以執(zhí)行了,該任務(wù)才會進入主線程,當我們打開網(wǎng)站時,像圖片的加載,音樂的加載,其實就是一個異步任務(wù)
/**
* 任何函數(shù)的聲明和任何變量的聲明都不會壓入調(diào)用棧(Call Stack)
* 將console.log(1)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 將setTimeout壓入調(diào)用棧執(zhí)行,執(zhí)行setTimeout,因為setTimeout是異步,所以將a()壓入Web APIs后,從調(diào)用棧中移除,倒計時等待
* 將console.log(5)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 將setTimeout壓入調(diào)用棧執(zhí)行,執(zhí)行setTimeout,因為setTimeout是異步,所以將c()壓入Web APIs后,從調(diào)用棧中移除,倒計時等待
* 將console.log(6)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 1秒倒計時結(jié)束后,將a()壓入消息隊列,Event Loop(事件循環(huán))監(jiān)聽到后,將a()壓入調(diào)用棧
* 將console.log(2)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 將a()里的setTimeout壓入調(diào)用棧執(zhí)行,執(zhí)行setTimeout,因為setTimeout是異步,所以將b()壓入Web APIs后,從調(diào)用棧中移除,倒計時等待
* 0.5秒倒計時結(jié)束后,將b()壓入消息隊列,Event Loop(事件循環(huán))監(jiān)聽到后,將b()壓入調(diào)用棧
* 將console.log(3)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 2秒倒計時結(jié)束后,將c()壓入消息隊列,Event Loop(事件循環(huán))監(jiān)聽到后,將c()壓入調(diào)用棧
* 將console.log(4)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
*/
// 異步代碼
console.log(1);
setTimeout(
(a = () => {
console.log(2);
setTimeout(
(b = () => {
console.log(3);
}),
500,
);
}),
1000,
);
console.log(5);
setTimeout(
(c = () => {
console.log(4);
}),
2000,
);
console.log(6);
/**
* 輸出結(jié)果打印
* 1
* 5
* 6
* 2
* 3
* 4
*/
同步模式
單線程
概念
JavaScript 是一門單線程的語言,因此,JavaScript 在同一個時間只能做一件事,單線程意味著,如果在同個時間有多個任務(wù)的話,這些任務(wù)就需要進行排隊,前一個任務(wù)執(zhí)行完,才會執(zhí)行下一個任務(wù)
使用單線程原因
JavaScript 的單線程,與它的用途是有很大關(guān)系,我們都知道,JavaScript 作為瀏覽器的腳本語言,主要用來實現(xiàn)與用戶的交互,利用 JavaScript,我們可以實現(xiàn)對 DOM 的各種各樣的操作,如果 JavaScript 是多線程的話,一個線程在一個 DOM 節(jié)點中增加內(nèi)容,另一個線程要刪除這個 DOM 節(jié)點,那么這個 DOM 節(jié)點究竟是要增加內(nèi)容還是刪除呢?這會帶來很復(fù)雜的同步問題,因此,JavaScript 是單線程的
同步任務(wù)
同步任務(wù)是指在主線程上排隊執(zhí)行的任務(wù),只有前一個任務(wù)執(zhí)行完畢,才能繼續(xù)執(zhí)行下一個任務(wù),當我們打開網(wǎng)站時,網(wǎng)站的渲染過程,比如元素的渲染,其實就是一個同步任務(wù)
// 同步代碼
/**
* 任何函數(shù)的聲明和任何變量的聲明都不會壓入調(diào)用棧(Call Stack)
* 將console.log(1)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 將console.log(3)壓入調(diào)用棧執(zhí)行,執(zhí)行后移除
* 將a()壓入調(diào)用棧執(zhí)行,執(zhí)行console.log(2),將console.log(2)壓入調(diào)用棧執(zhí)行,執(zhí)行后依次移除
*/
console.log(1);
const a = () => console.log(2);
console.log(3);
a();
/**
* 輸出結(jié)果打印
* 1
* 3
* 2
*/
副作用
因為 JavaScript 是單線程,因此同個時間只能處理同個任務(wù),所有任務(wù)都需要排隊,前一個任務(wù)執(zhí)行完,才能繼續(xù)執(zhí)行下一個任務(wù),但是,如果前一個任務(wù)的執(zhí)行時間很長,比如文件的讀取操作或 ajax 操作,后一個任務(wù)就不得不等著,拿 ajax 來說,當用戶向后臺獲取大量的數(shù)據(jù)時,不得不等到所有數(shù)據(jù)都獲取完畢才能進行下一步操作,用戶只能在那里干等著,嚴重影響用戶體驗
Promise
Promise 對象用于表示一個異步操作的最終完成 (或失敗), 及其結(jié)果值.
參數(shù)
executor
executor 是帶有 resolve 和 reject 兩個參數(shù)的函數(shù) 。Promise 構(gòu)造函數(shù)執(zhí)行時立即調(diào)用 executor 函數(shù), resolve 和 reject 兩個函數(shù)作為參數(shù)傳遞給 executor(executor 函數(shù)在 Promise 構(gòu)造函數(shù)返回所建 promise 實例對象前被調(diào)用)。resolve 和 reject 函數(shù)被調(diào)用時,分別將 promise 的狀態(tài)改為 fulfilled(完成)或 rejected(失敗)。executor 內(nèi)部通常會執(zhí)行一些異步操作,一旦異步操作執(zhí)行完畢(可能成功/失敗),要么調(diào)用 resolve 函數(shù)來將 promise 狀態(tài)改成 fulfilled,要么調(diào)用 reject 函數(shù)將 promise 的狀態(tài)改為 rejected。如果在 executor 函數(shù)中拋出一個錯誤,那么該 promise 狀態(tài)為 rejected。executor 函數(shù)的返回值被忽略。
描述
Promise 對象是一個代理對象(代理一個值),被代理的值在 Promise 對象創(chuàng)建時可能是未知的。它允許你為異步操作的成功和失敗分別綁定相應(yīng)的處理方法(handlers)。 這讓異步方法可以像同步方法那樣返回值,但并不是立即返回最終執(zhí)行結(jié)果,而是一個能代表未來出現(xiàn)的結(jié)果的 promise 對象
一個 Promise 有以下幾種狀態(tài):
- pending: 初始狀態(tài),既不是成功,也不是失敗狀態(tài)。
- fulfilled: 意味著操作成功完成。
- rejected: 意味著操作失敗。
pending 狀態(tài)的 Promise 對象可能會變?yōu)?fulfilled 狀態(tài)并傳遞一個值給相應(yīng)的狀態(tài)處理方法,也可能變?yōu)槭顟B(tài)(rejected)并傳遞失敗信息。當其中任一種情況出現(xiàn)時,Promise 對象的 then 方法綁定的處理方法(handlers )就會被調(diào)用(then 方法包含兩個參數(shù):onfulfilled 和 onrejected,它們都是 Function 類型。當 Promise 狀態(tài)為 fulfilled 時,調(diào)用 then 的 onfulfilled 方法,當 Promise 狀態(tài)為 rejected 時,調(diào)用 then 的 onrejected 方法, 所以在異步操作的完成和綁定處理方法之間不存在競爭)。
因為 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 對象, 所以它們可以被鏈式調(diào)用。
demo
// Promise基本使用
let promise = new Promise((resolve, reject) => {
// resolve('成功');
reject('失敗');
});
promise.then(
(res) => {
console.log(res);
},
(e) => {
console.log(e);
},
);
const Ajax = (url) => {
// promise方式Ajax使用
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
};
Ajax('fed-e-task-01-01/notes/promise/api/user.json').then(
(value) => {
console.log(value);
},
(e) => {
console.log(e);
},
);
// promise鏈式調(diào)用
Ajax('fed-e-task-01-01/notes/promise/api/user.json')
.then((value) => {
console.log(value);
return Ajax('fed-e-task-01-01/notes/promise/api/class.json');
})
.then((value) => {
console.log(value);
return 'abc';
})
.then((value) => console.log(value));
promise 靜態(tài)方法使用
- all 方法返回一個 Promise 實例,此實例在 iterable 參數(shù)內(nèi)所有的 promise 都“完成(resolved)”或參數(shù)中不包含 promise 時回調(diào)完成(resolve);如果參數(shù)中 promise 有一個失敗(rejected),此實例回調(diào)失敗(reject),失敗的原因是第一個失敗 promise 的結(jié)果
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
/**
* 輸出結(jié)果
* [3, 42, "foo"]
*/
- race 返回一個 promise,一旦迭代器中的某個 promise 解析或拒絕,返回的 promise 就會解析或拒絕。
const promise4 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise5 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise4, promise5]).then((value) => {
console.log(value);
// 都解析了,但是promise2更快
});
/**
* 輸出結(jié)果
* two
*/
- reject 方法返回一個帶有拒絕原因的 Promise 對象
Promise.reject(new Error('fail')).catch((e) => console.error(e.message()));
/**
* 輸出結(jié)果
* fail
*/
- resolve 方法返回一個以給定值解析后的 Promise 對象。如果這個值是一個 promise ,那么將返回這個 promise ;如果這個值是 thenable(即帶有"then" 方法),返回的 promise 會“跟隨”這個 thenable 的對象,采用它的最終狀態(tài);否則返回的 promise 將以此值完成。此函數(shù)將類 promise 對象的多層嵌套展平
Promise.resolve('success').then((res) => console.log(res));
/**
* 輸出結(jié)果
* success
*/
Event Loop(事件循環(huán))
Event Loop 它最主要是分三部分:主線程、宏任務(wù)(macrotask)、微任務(wù)(microtask)
宏任務(wù)
宏任務(wù),macrotask,也叫 tasks。 一些異步任務(wù)的回調(diào)會依次進入 macro task queue,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
微任務(wù)
微任務(wù),microtask,也叫 jobs。 另一些異步任務(wù)的回調(diào)會依次進入 micro task queue,等待后續(xù)被調(diào)用,這些異步任務(wù)包括:
- Promise
- Object.observe
- MutationObserver
- process.nextTick
執(zhí)行順序
主線程 > 微任務(wù) > 宏任務(wù)
Generator(生成器)
生成器對象是由一個 generator function 返回的,并且它符合可迭代協(xié)議和迭代器協(xié)議。
function* gen() {
yield 1;
yield 2;
yield 3;
}
let g = gen();
// "Generator { }"
方法
Generator.prototype.next()
- 返回一個由 yield 表達式生成的值。
const arr = [1, 2, 3];
function* idMaker() {
for (let val of arr) {
yield val;
}
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);
/**
* 輸出結(jié)果
* 1
* 2
* 3
*/
Generator.prototype.return()
- 返回給定的值并結(jié)束生成器。
const arr = [1, 2, 3];
function* idMaker() {
for (let val of arr) {
yield val;
}
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
console.log(gen.return('結(jié)束').value);
console.log(gen.next().value);
console.log(gen.next().value);
/**
* 輸出結(jié)果
* 1
* Uncaught Error: error
* undefined
* undefined
*/
Generator.prototype.throw()
- 向生成器拋出一個錯誤。
const arr = [1, 2, 3];
function* idMaker() {
for (let val of arr) {
yield val;
}
}
let gen = idMaker(); // "Generator { }"
console.log(gen.next().value);
// 阻止生成器函數(shù)往下運行,并拋出異常
gen.throw(new Error('error'));
console.log(gen.next().value);
console.log(gen.next().value);
/**
* 輸出結(jié)果
* 1
* Uncaught Error: error
*/
demo
// generator配合promise使用
const Ajax = (url) => {
// promise方式Ajax使用
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
};
function* main() {
try {
const result = yield Ajax('fed-e-task-01-01/notes/promise/api/user.json');
console.log(result);
const result1 = yield Ajax('fed-e-task-01-01/notes/promise/api/class.json');
console.log(result1);
} catch (e) {
console.error(e);
}
}
const co = (generator) => {
const g = generator();
function handelResult(result) {
if (result.done) return;
result.value.then(
(res) => {
handelResult(g.next(res));
},
(e) => {
handelResult(g.throw(e));
},
);
}
handelResult(g.next());
};
co(main);
Async 函數(shù)
async function 用來定義一個返回 AsyncFunction 對象的異步函數(shù)。異步函數(shù)是指通過事件循環(huán)異步執(zhí)行的函數(shù),它會通過一個隱式的 Promise 返回其結(jié)果。如果你在代碼中使用了異步函數(shù),就會發(fā)現(xiàn)它的語法和結(jié)構(gòu)會更像是標準的同步函數(shù)。
語法
async function name([param[, param[, ... param]]]) { statements }
描述
一個 async 異步函數(shù)可以包含 await 指令,該指令會暫停異步函數(shù)的執(zhí)行,并等待 Promise 執(zhí)行,然后繼續(xù)執(zhí)行異步函數(shù),并返回結(jié)果。
記住,await 關(guān)鍵字只在異步函數(shù)內(nèi)有效。如果你在異步函數(shù)外使用它,會拋出語法錯誤。
注意,當異步函數(shù)暫停時,它調(diào)用的函數(shù)會繼續(xù)執(zhí)行(收到異步函數(shù)返回的隱式 Promise)
const Ajax = (url) => {
// promise方式Ajax使用
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
};
async function main() {
try {
const result = await Ajax('fed-e-task-01-01/notes/promise/api/user.json');
console.log(result);
const result1 = await Ajax('fed-e-task-01-01/notes/promise/api/class.json');
console.log(result1);
} catch (e) {
console.error(e);
}
}
main();
Promise
描述
Promise 是一個類,參數(shù)是一個執(zhí)行器函數(shù), 執(zhí)行器函數(shù)自執(zhí)行。
Promise 有 3 個狀態(tài) Pending 默認等待態(tài) 、Fulfilled 成功態(tài) 、Rejected 失敗態(tài) 狀態(tài)一改變就不能再次修改 Pending -> Fulfilled || Pending -> Rejected
執(zhí)行器函數(shù)參數(shù)有 resolve 方法和 reject 方法 resolve 方法將 Pending 3.> Fulfilled reject 方法將 Rejected -> Rejected resolve 方法的參數(shù)將作為 then 方法成功回調(diào)的值, reject 方法的參數(shù)將作為 then 方法失敗回調(diào)的原因。
then 方法有兩個參數(shù) 一個是成功回調(diào)的函數(shù) successCallback,一個是失敗回調(diào)的函數(shù) failCallback。 promise 的成功態(tài)會將成功的值傳給成功的回調(diào)并且執(zhí)行,失敗態(tài)會將失敗的原因傳遞給失敗的回調(diào)并執(zhí)行。
執(zhí)行器中 resolve 和 reject 在異步中執(zhí)行的時候,當前狀態(tài)還是等待態(tài) 需要把 then 方法的成功回調(diào)和失敗回調(diào)存起來,等異步調(diào)用 resolve 的時候調(diào)用成功回調(diào),reject 的時候調(diào)用失敗回調(diào)
-
then 方法
a. then 方法多次調(diào)用添加多個處理函數(shù);
b. 實現(xiàn) then 的鏈式調(diào)用: then 方法鏈式調(diào)用識別 promise 自返回 then 鏈式調(diào)用 返回的是一個新的 promise 對象
c. 判斷 then 方法成功回調(diào)和失敗回調(diào)的返回值 x x 返回的是一個 pormise,判斷 x 和當前 then 返回是不是同一個 promise,如果是同一個 promise 就報錯。x 和 then 返回的不是同一個 promise,將 x 的 then 方法執(zhí)行返回給下一個 then。 x 是常量直接當作下一個 then 的成功回調(diào)的參數(shù)。后面 then 方法的回調(diào)函數(shù)拿到值的是上一個 then 方法的回調(diào)函數(shù)的返回值。
d.捕獲錯誤及 then 鏈式調(diào)用其他狀態(tài)代碼補充
e. then 方法的參數(shù)可選 ?
f. then 方法的值穿透
executor 執(zhí)行器函數(shù) / then 方法可能有錯誤,有錯誤直接調(diào)用 reject 方法
-
all 和 race 方法的實現(xiàn)
all 和 race 方法不管 promise 成功還是失敗都不會影響其他 promise 執(zhí)行 ? all 方法返回一個新的 promise? all 參數(shù)里面每一項執(zhí)行完成,才把所有結(jié)果依次按原順序 resolve 出去 ? all 方法只要一個 promise 失敗就 reject 失敗的 promise 結(jié)果 ? Promise.race 方法,誰執(zhí)行的快就返回誰
resolve 和 reject 方法的實現(xiàn)
resolve 方法: 相當于實例化 Promise 對象,并且調(diào)用了 resolve 方法 reject 方法:相當于實例化 Promise 對象,并且調(diào)用了 reject 方法
finally 方法的實現(xiàn)
finally 的實現(xiàn),不管是成功狀態(tài)還是失敗態(tài)都會進這個 finally 方法 (等待態(tài)不會進)。finally 方法會返回一個新的 promise,它拿不到上次 then 執(zhí)行的結(jié)果(所以沒有參數(shù)),內(nèi)部會手動執(zhí)行一次 promise 的 then 方法。finally 方法有錯誤會把錯誤作為下次 then 方法的失敗回調(diào)的參數(shù)。
-
catch 方法的實現(xiàn)
catch 方法相當于執(zhí)行 then 方法的失敗回調(diào)
demo
自定義 promise
class Promise {
/**
* 構(gòu)造函數(shù)
* @param executor {Function} 執(zhí)行器
*/
constructor(executor) {
/**
* 初始狀態(tài),既不是成功,也不是失敗狀態(tài)
* @type {string}
*/
this.PENDING = 'pending';
/**
* 意味著操作成功完成
* @type {string}
*/
this.FULFILLED = 'fulfilled';
/**
* 意味著操作失敗
* @type {string}
*/
this.REJECTED = 'rejected';
/**
* Promise 狀態(tài)
* @type {string} 默認值PENDING
*/
this.status = this.PENDING;
/**
* 成功回調(diào)函數(shù)入?yún)⒌闹? * @type {string} 默認值undefined
*/
this.value = undefined;
/**
* 失敗回調(diào)函數(shù)入?yún)⒌闹? * @type {string} 默認值undefined
*/
this.reason = undefined;
/**
* successCallback 成功回調(diào)函數(shù)
* @type {Array} 默認值[]
*/
this.successCallback = [];
/**
* failCallback 失敗回調(diào)函數(shù)
* @type {Array} 默認值[]
*/
this.failCallback = [];
/**
* privateResolve 解析
* @param value 成功回調(diào)的入?yún)? */
this.privateResolve = (value) => {
// 在promise的狀態(tài)為PENDING時,允許改變其狀態(tài)
if (this.status === this.PENDING) {
// 觸發(fā)resolve,將promise的狀態(tài)改為fulfilled(完成)
this.status = this.FULFILLED;
// 將resolve傳遞的參數(shù),保存在Promise對象的value里
this.value = value;
// 如果successCallback成功回調(diào)函數(shù)存在,則執(zhí)行
this.successCallback.forEach((callback) => {
callback();
});
}
};
/**
* privateReject 駁回
* @param reason 失敗回調(diào)入?yún)? */
this.privateReject = (reason) => {
// 在promise的狀態(tài)為PENDING時,允許改變其狀態(tài)
if (this.status === this.PENDING) {
// 觸發(fā)reject,將promise的狀態(tài)改為rejected(失敗)
this.status = this.REJECTED;
// 將reject傳遞的參數(shù),保存在MyPromise對象的reason里
this.reason = reason;
// 如果failCallback失敗回調(diào)函數(shù)存在,則執(zhí)行
this.failCallback.forEach((callback) => {
callback();
});
}
};
// 捕獲執(zhí)行器異常,并從reject拋出
try {
executor(this.privateResolve, this.privateReject);
} catch (e) {
this.privateReject(e);
}
}
then(successCallback, failCallback) {
// 判斷successCallback和failCallback是否存在,不存在將值向下傳遞
successCallback = successCallback ? successCallback : (value) => value;
// failCallback = failCallback ? failCallback : (reason) => reason;
failCallback = failCallback
? failCallback
: (reason) => {
throw reason;
};
// 鏈式調(diào)用實現(xiàn),返回Promise函數(shù)對象
let promise = new Promise((resolve, reject) => {
// 判斷promise的狀態(tài),當其狀態(tài)為FULFILLED時,觸發(fā)成功回調(diào)函數(shù);當其狀態(tài)為REJECTED時,觸發(fā)成功回調(diào)函數(shù)
// 后面的 then 方法的回調(diào)函數(shù)的入?yún)⑷∽陨弦粋€ then 方法的回調(diào)函數(shù)的返回值
// promise對象未生成,所以將resolvePromise放到異步里調(diào)用,等待promise對象未生成后執(zhí)行
if (this.status === this.FULFILLED) {
// 捕獲異常,并從reject拋出
setTimeout(() => {
try {
resolvePromise(
promise,
successCallback(this.value),
resolve,
reject,
);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.status === this.REJECTED) {
// 捕獲異常,并從reject拋出
setTimeout(() => {
try {
resolvePromise(promise, failCallback(this.reason), resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
// 當判斷promise的狀態(tài),當其狀態(tài)為PENDING時,存儲回調(diào)函數(shù)
this.successCallback.push(() => {
// 捕獲異常,并從reject拋出
setTimeout(() => {
try {
resolvePromise(
promise,
successCallback(this.value),
resolve,
reject,
);
} catch (e) {
reject(e);
}
}, 0);
});
this.failCallback.push(() => {
// 捕獲異常,并從reject拋出
setTimeout(() => {
try {
resolvePromise(
promise,
failCallback(this.reason),
resolve,
reject,
);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
return promise;
}
/**
* finally 等待Promise對象執(zhí)行完所有任務(wù),并執(zhí)行
* @param callback 回調(diào)函數(shù)
* @returns {Promise} 返回Promise對象
*/
finally(callback) {
return this.then(
(value) => {
return Promise.resolve(callback()).then(() => value);
},
(reason) => {
return Promise.resolve(callback()).then(() => {
throw reason;
});
},
);
}
/**
* catch 返回失敗回調(diào)函數(shù)
* @param callback 回調(diào)函數(shù)
* @returns {Promise} 返回Promise對象
*/
catch(callback) {
return this.then(undefined, callback);
}
/**
* 等待所有Promise對象執(zhí)行完畢,并返回結(jié)果
* @param array {Array} 數(shù)組對象
* @returns {Promise} 返回Promise對象
*/
static all(array) {
// 返回數(shù)組
let result = [];
// 計數(shù)器
let index = 0;
return new Promise((resolve, reject) => {
/**
* addResult 向result數(shù)組添加返回值
* @param key {Number} key值
* @param value {Object} value值
*/
const addResult = (key, value) => {
result[key] = value;
index++;
// 判斷所有異步操作完成后,執(zhí)行resolve
if (array.length === index) {
resolve(result);
}
};
// 遍歷
array.forEach((val, i) => {
// 判斷當前值,是否屬于Promise對象
if (val instanceof Promise) {
// Promise對象
// 執(zhí)行成功回調(diào)函數(shù),獲取返回值,并添加到result數(shù)組里
// 執(zhí)行失敗回調(diào)函數(shù),直接通過reject拋出失敗原因
val.then(
(value) => addResult(i, value),
(reason) => reject(reason),
);
} else {
// 普通對象,將當前值添加到result數(shù)組里
addResult(i, val);
}
});
});
}
/**
* resolve 解析
* @param value
* @returns {Promise}
*/
static resolve(value) {
// 判斷value是否是Promise對象,是則直接返回,否則創(chuàng)建Promise對象并返回
if (value instanceof Promise) return value;
return new Promise((resolve) => resolve(value));
}
/**
* reject 駁回
* @param value
* @returns {Promise}
*/
static reject(value) {
// 判斷value是否是Promise對象,是則直接返回,否則創(chuàng)建Promise對象并返回
if (value instanceof Promise) return value;
return new Promise((undefined, reject) => reject(value));
}
/**
* 返回一個 promise,一旦迭代器中的某個 promise 解析或拒絕,返回的 promise 就會解析或拒絕。
* @param array {Array} 數(shù)組對象
* @returns {Promise} 返回Promise對象
*/
static race(array) {
return new Promise((resolve, reject) => {
// 遍歷
array.forEach((val, i) => {
// 判斷當前值,是否屬于Promise對象
if (val instanceof Promise) {
// Promise對象
// 執(zhí)行成功回調(diào)函數(shù),獲取返回值,并通過resolve彈出
// 執(zhí)行失敗回調(diào)函數(shù),直接通過reject拋出失敗原因
val.then(
(value) => resolve(value),
(reason) => reject(reason),
);
} else {
// 普通對象,將當前值通過resolve彈出
resolve(val);
}
});
});
}
}
/**
* resolvePromise 解析Promise
* @param promise promise對象
* @param result 上一個then回調(diào)函數(shù)返回值
* @param resolve 解析函數(shù)
* @param reject 駁回函數(shù)
*/
const resolvePromise = (promise, result, resolve, reject) => {
// 判斷promise與result是否相等,如果相等,則是promise循環(huán)調(diào)用,這里應(yīng)該拋出異常,并阻止往下執(zhí)行
if (promise === result) {
return reject(new TypeError('Chaining cycle detected for my-promise'));
}
// 判斷result是普通對象還是屬于Promise對象
if (result instanceof Promise) {
// 查看Promise對象返回結(jié)果,調(diào)用對應(yīng)的resolve或reject
result.then(resolve, reject);
} else {
resolve(result);
}
};
module.exports = Promise;
調(diào)用
const Promise = require('./promise');
const Ajax = (url) => {
// promise方式Ajax使用
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
};
// Promise基本使用
let promise = new Promise((resolve, reject) => {
// resolve('成功');
reject('失敗');
});
promise
.then((res) => {
console.log(res);
})
.catch((e) => console.error(e));
// promise方式Ajax使用
Ajax('fed-e-task-01-01/notes/promise/api/user.json').then(
(value) => {
console.log(value);
},
(e) => {
console.error(e);
},
);
// promise鏈式調(diào)用
Ajax('fed-e-task-01-01/notes/promise/api/user.json')
.then((value) => {
console.log(value);
return Ajax('fed-e-task-01-01/notes/promise/api/class.json');
})
.then((value) => {
console.log(value);
return 'abc';
})
.then((value) => console.log(value));
// promise靜態(tài)方法使用
// all 方法返回一個 Promise 實例,此實例在 iterable 參數(shù)內(nèi)所有的 promise 都“完成(resolved)”或參數(shù)中不包含 promise 時回調(diào)完成(resolve);如果參數(shù)中 promise 有一個失敗(rejected),此實例回調(diào)失敗(reject),失敗的原因是第一個失敗 promise 的結(jié)果
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values);
});
/**
* 輸出結(jié)果
* [3, 42, "foo"]
*/
// race返回一個 promise,一旦迭代器中的某個promise解析或拒絕,返回的 promise就會解析或拒絕。
const promise4 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise5 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise4, promise5]).then((value) => {
console.log(value);
// Both resolve, but promise5 is faster
});
/**
* 輸出結(jié)果
* two
*/
// reject方法返回一個帶有拒絕原因的Promise對象
Promise.reject(new Error('fail')).catch((e) => console.error(e.message));
/**
* 輸出結(jié)果
* fail
*/
// resolve方法返回一個以給定值解析后的Promise 對象。如果這個值是一個 promise ,那么將返回這個 promise ;如果這個值是thenable(即帶有"then" 方法),返回的promise會“跟隨”這個thenable的對象,采用它的最終狀態(tài);否則返回的promise將以此值完成。此函數(shù)將類promise對象的多層嵌套展平
Promise.resolve('success').then((res) => console.log(res));
/**
* 輸出結(jié)果
* success
*/