代码示例:
//面向过程编程
var a=2;
var b=4;
var sum=a+b;
console.log(sum);
//函数式编程
function add(a,b){
return a+b;
}
console.log(add(4,5));
在JavaScript
中函数就是一个普通的对象(可以通过new Function()
创建),所以函数有以下三个特性:
//把函数赋值给变量
var fn=function(){
console.log('函数表达式');
}
fn();
function get_name(name){
console.log(`我是${name}`);
}
//name包裹了get_name且name和get_name的参数和返回值都一样,所以可以看做name和get_name是一样的函数
var obj={
name(name){return get_name(name)},
}
console.log(obj.name)
//优化
var obj1={
name:get_name//把函数get_name赋值给函数name
}
console.log(obj1.name)
foreach
方法:用于调用数组的每个元素,并将元素传递给回调函数const arr=[1,2,3,4,5];
/**
* @description: 数组的foreach方法使用
*/
arr.forEach(function(item,index){
console.log(`第${index}项是${item}`);
})
/**
* @description: 模拟数组的foreach方法
* @param {*} array
* @param {*} fn
*/
function forEach(array, fn) {
for (var i = 0; i < array.length; i++) {
fn(array[i]);
}
}
/**
* 测试forEach
*/
forEach(arr,function(item){
console.log(item)
})
filter
方法:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,不会改变原始数组/**
* @description:数组的filter方法使用
*/
let even_number=arr.filter(function(item,index){
return item%2==0;
})
console.log(even_number)
/**
* @name:filter
* @description: 模拟数组的filter方法
* @param {*} array
* @param {*} fn
*/
function filter(array,fn){
var result=[];
for (var i = 0; i < array.length; i++) {
if(fn(array[i],i)){
result.push(array[i]);
}
}
return result;
}
/**
* 测试filter
*/
let res=filter(arr,function(item,index){
return item%2===0&&index!=0;
})
console.log(res)
lodash
中的once
函数/**
* @name:once
* @description: 模拟lodash中的once函数
* @param {*} fn
*/
function once (fn) {
var done = false;
return function (){
if(!done){
done = true;
// 下面这两种写法都可以,就是将arguments展开传递给fn
// fn(...arguments);
return fn.apply(this,arguments);
}
}
}
/**
* 测试once
*/
var pay = once (function (money){
console.log(`支付了:${money}元`);
});
pay(2);
pay(3);
pay(4);
// 以上的执行只输出了:支付了:2元
console.log(pay)//是once函数的返回值(匿名函数)
// 也可以使用下面的方式执行
once (function (money){
console.log(`支付了:${money}元`);
})(2)
once (function (money){
console.log(`支付了:${money}元`);
})(5)
// 以上的执行只输出了:支付了:2元、支付了:5元
补充:call、bing、apply
call 、bind 、 apply 这三个函数的第一个参数都是 this 的指向对象。
第二个参数:
call的第二个参数是用逗号分隔,直接放到后面,例如:obj.myFun.call(this,'参数一', ... ,'参数n' )。
apply的第二个参数是以数组的形式传入,例如:obj.myFun.apply(this,['参数一', ... ,'参数n'])。
bind除了返回的是函数以外,它的参数传递方式和 call 一样。
forEach、map、filter、 every、some、find/findIndex、reduce、sort等
map
:返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,不会改变原始数组。const arr = [1, 2, 3, 4, 5];
/**
* @description: 数组的map方法
*/
let map_res = arr.map(function (item, index) {
return item + index;
})
// console.log(map_res)
/**
* @name:map
* @description: 模拟数组的map方法
* @param {*} array
* @param {*} fn
*/
function map(array, fn) {
let res = [];
for (var i = 0; i < array.length; i++) {
res.push(fn(array[i]));
}
return res;
}
/**
* 测试map
*/
console.log(map(arr, function (item) {
return item + 1;
}))
every
:用于检测数组所有元素是否都符合指定条件(通过函数提供),不会改变原始数组
false
,且剩余的元素不会再进行检测。true
。const arr = [1, 2, 3, 4, 5];
/**
* @description: 数组every方法
*/
let every_res = arr.every(function (item, index) {
return item % 2 === 0;
})
// console.log(every_res)
/**
* @name:every
* @description: 模拟数组的every方法
* @param {*} array
* @param {*} fn
*/
function every(array, fn) {
let res = true;
for (var i = 0; i < array.length; i++) {
res = fn(array[i]);
if (!res) {//只要有一个为false则不再循环,直接返回false
break;
}
}
return res;
}
/**
* 测试every
*/
console.log(every(arr, function (item) {
return item >= 3;
}))
some
:用于检测数组中的元素是否满足指定条件(通过函数提供),不会改变原始数组。
true
, 剩余的元素不会再执行检测。false
。/**
* @description: 数组some方法
*/
let some_res = arr.some(function (item, index) {
return item % 2 === 0;
})
console.log(some_res)
/**
* @name:some
* @description: 模拟数组的some方法
* @param {*} array
* @param {*} fn
*/
function some(array, fn) {
let res = false;
for (var i = 0; i < array.length; i++) {
res = fn(array[i]);
if (res) {//只要有一个为false则不再循环,直接返回false
break;
}
}
return res;
}
/**
* 测试some
*/
console.log(some(arr, function (item) {
return item >= 3;
}))
find/findIndex
:find
返回满足条件的第一个元素值、findIndex
返回满足条件的第一个索引值,不会改变原始数组
true
时, find()
返回符合条件的元素,之后的值不会再调用执行函数。undefined
/**
* @description: 数组find方法
*/
let find_res = arr.find(function (item, index) {
return item >= 7;
})
// console.log(find_res)////true、undefined
/**
* @name:find
* @description: 模拟数组的find方法
* @param {*} array
* @param {*} fn
*/
function find(array, fn) {
let res = undefined;
let falg=false;
for (var i = 0; i < array.length; i++) {
falg = fn(array[i]);
if (falg) {//只要有一个为true则不再循环,直接返回当前的元素值
res=array[i];
break;
}
}
return res;
}
/**
* 测试find
*/
// console.log(find(arr, function (item) {
// return item >= 7;
// }))//true、undefined
console.log(find(arr, function (item) {
return item >= 3;
}))//true、3
闭包:函数和其周围的状态(词法环境)的引用捆绑在一起形成闭包。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。
//函数作为返回值
function makeFn(){
let msg='msg';
return function(){
console.log(msg);//内部函数访问了外部函数的变量形成了闭包
}
}
const fn=makeFn();
fn();
在上述代码中形成了闭包:
slice
和splice
分别是:纯函数和不纯函数
slice
返回数组中的指定部分,不会改变原数组splice
对数组进行操作返回该数组,会改变原数组let arr = [1, 2, 3, 4];
// 纯函数
/**
* @name:array.slice
* @description: 用于从已有的数组中返回选定的元素,返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素
* @param {*} start
* start:可选。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推
* @param {*} end
* end:可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
*/
console.log(arr.slice(0, 3))//[1,2,3]
console.log(arr.slice(0, 3))//[1,2,3]
console.log(arr.slice(0, 3))//[1,2,3]
console.log(arr.slice(-1, 3))//[]
console.log(arr.slice(0, -3))//[1]
/**
* @name:getSum
* @description:
* @param {*} a
* @param {*} b
*/
function getSum(a,b){
return a+b;
}
console.log(getSum(1,2))//3
console.log(getSum(1,2))//3
console.log(getSum(1,2))//3
// 不纯函数
/**
* @name:array.splice
* @description: 用于添加或删除数组中的元素,会改变原始数组,如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组。
* @param {*} start
* start:必需。规定从何处添加/删除元素。该参数是开始插入和(或)删除的数组元素的下标,必须是数字
* @param {*} howmany
* howmany:可选。规定应该删除多少元素。必须是数字,但可以是 "0"。如果未规定此参数,则删除从 index 开始到原数组结尾的所有元素。
* @param {*} item1,.....,itemX
* item1,.....,itemX:可选。要添加到数组的新元素
*/
console.log(arr.splice(0, 3))//[1,2,3]
console.log(arr.splice(0, 3))//[4]
console.log(arr.splice(0, 3))//[]
副作用让一个函数变的不纯(如下面代码所示),纯函数的根据是相同的输入始终得到相同的输出,如果函数依赖于外部的状态就无法保证输出相同,就会带来副作用。
代码示例:
// 不纯的
let mini=18;
function checkAge(age){
return age>=mini;
}
// 纯的(有硬编码,可以通过柯里化解决)
function checkAge(age){
let mini=18;
return age>=mini;
}
副作用来源:
所有的外部交互都可能带来副作用,副作用也使得方法的通用性下降,不适合扩展和可重用性。同时副作用会给程序带来安全隐患,给程序带来不确定性,但是副作用不可能完全禁止,只能尽可能的使其控制在可控范围内。
lodash
是一个一致性、模块化、高性能的 JavaScript
实用工具库。
lodash
中的fp
模块提供的方法是已经被柯里化的、如果一个方法的 参数是函数的话,要求函数优先,数据之后。
const _=require('lodash');
const fp=require('lodash/fp');
//数据优先,函数之后
console.log(_.map(['a','b','c'],_.toUpper))//[ 'A', 'B', 'C' ]
console.log(_.split('h_j','_'))
// 函数优先,数据之后
fp.map(fp.toUpper,['a','b','c'])//[ 'A', 'B', 'C' ]
//已经被柯里化了
console.log(fp.split('_','h_j'))
console.log(fp.split('_')('h_j'))
parseInt()
函数可解析一个字符串,并返回一个整数。
parseInt(string, radix)
string
:必需。要被解析的字符串。radix
:可选。表示要解析的数字的基数。该值介于 2 ~ 36
之间。如果省略该参数或其值为 0
,则数字将以 10
为基础来解析。const _=require('lodash');
const fp=require('lodash/fp');
//lodash的map方法中的函数接收的参数是三个(item,index,array)
console.log(_.map(['123','123b','1'],parseInt))//[ 123, NaN, 1 ]
//fp中的map方法的函数接收的参数只有一个(item)
console.log(fp.map(parseInt,['123','123b','1']))//[ 123, 123, 1 ]
柯里化指的是当一个函数有多个参数的时候先传递一部分参数调用它(这部分参数以后永远不变),然后返回一个新的函数接收剩余的参数,并且返回函数的执行结果。
代码示例:
// 不纯的
let mini=18;
function checkAge(age){
return age>=mini;
}
// 普通的纯函数
function checkAge(mini,age){
return age>=mini;
}
console.log(checkAge(18,20));
console.log(checkAge(18,17))
// 柯里化
function checkAge(mini){
return function(age){
return age>=mini;
}
}
const checkAge18=checkAge(18);
const checkAge20=checkAge(20);
console.log(checkAge18(20))
console.log(checkAge20(17));
function
参数,如果function
的参数都被提供,则执行function
并返回执行的结果。否则继续返回该函数并等待接收剩余的参数const _=require('lodash');
function 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));
参数有形参(parameter
)和实参(argument
)的区别,形参相当于函数中定义的变量,实参是在运行时的函数调用时传入的参数。
说明白就是,形参就是函数声明时的变量,实参是我们调用该函数时传入的具体参数。
声明函数add
时,a
,b
就是形参。调用函数add(1,2)
:1
,2
就是实参。
代码示例:
function add(a,b) {
return a + b
};
add(1,2);
剩余参数语法允许我们将一个不定数量的参数表示为一个数组。
如果一个命名参数为(...theArgs
),则它将成为一个由剩余参数组成的真数组,其中从0
(包括)到theArgs.length
(排除)的元素由传递给函数的实际参数提供。
剩余参数和 arguments
对象之间的区别主要有三个:
arguments
对象包含了传给函数的所有实参。arguments
对象不是一个真正的数组,而剩余参数是真正的 Array
实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort
,map
,forEach
或pop
。arguments
对象还有一些附加的属性 (如callee
属性)。模拟lodash中的curry函数代码示例:
/**
* @name:curry
* @description: 模拟lodash中的curry函数
* @param {*} fn
*/
function curry(fn){
// console.log(fn.length)//返回fn函数的实际参数个数
return function curriedFn(...args){//这里行成了闭包,每次传入的参数被保存在args中
// console.log(args)
// 判断形参和实参的个数是否相同
if(args.length<fn.length){//如果形参小于实参则需要返回一个函数接收剩余的参数
return function(){
// 第一次传入的参数保存在 args中
// arguments是一个伪数组,使用Array.from可以将伪数组转换成数组
return curriedFn(args.concat(Array.from(arguments)));
}
}
return fn(...args);//如果形参大于等于实参则执行传入的fn函数
}
}
function getSum(a,b,c){
return a+b+c;
}
// 柯里化以后的函数
let curried=curry(getSum);
console.log(curried(1,2,3))
console.log(curried(1,2)(3))
函数组合(compose
):如果一个函数要经过多个函数处理才能才能得到最终值,这个时候可以把中间过程的函数合并成一个函数。
/**
* @name:compose
* @Description: 模拟组合函数,接收多个一元函数返回一个新的函数
* @param {...any} args function
*/
function compose(...args){//接收多个参数
return function(value){//返回一个函数,接收的参数是最后调用的时候传递的参数
//组合函数默认从右到左开始执行,所以需要将args先进行反转args.reverse()
//反转完成后一次开始执行,每一次执行完的结果传递给下一个函数作为参数,使用reduce
return args.reverse().reduce(function(total,fn){//fn是args中的每一项函数
return fn(total);
},value)
}
}
const compose= (...args) => (value=> args.reverse().reduce((total,fn)=>fn(total),value));
function reverse(array){
return array.reverse();
}
function first(array){
return array[0];
}
function toUpper(str){
return str.toUpperCase();
}
//反转
//获取第一个元素
//转成大写字母
const res=compose(toUpper,first,reverse);
console.log(res(['one','two','three']))
结合律: 按照函数执行的顺序不管把那几个函数结合在一起,其返回的结果都是一样的。
代码示例:
let fn=compose(f,g,h);
let bool=compose(compose(f,g),h)==compose(f,compose(g,h));
console.log(bool)//true
const _=require('lodash');
// log
const log=(res)=>{
console.log(res);
return res;
}
// 追踪
const trace=_.curry((tag,res)=>{//从右到左执行
console.log(`${tag}的结果是:${res}`);
return res;
})
// _.split()
// const split=_.curry(function(seq,str){
// return _.split(str,seq);
// })
const split=_.curry((seq,str)=>_.split(str,seq));
// join
const join=_.curry((seq,str)=>_.join(str,seq));
// map
const map=_.curry((fn,array)=>_.map(array,fn));
// const fn=_.flowRight(join('_'),_.toLower,split(' '));
// console.log(fn("HELLO WORLD"))//h_e_l_l_o_,_w_o_r_l_d_,
const fn=_.flowRight(join('_'),map(_.toLower),trace('split'),split(' '));
console.log(fn("HELLO WORLD"))//hello_world
函子是一个特殊的容器(包含值和值的变形关系-变形关系就是函数),通过一个普通的对象来实现,该对象具有map
方法,map
方法可以运行一个函数对值进行处理(变形关系)。
代码示例1:使用new
关键字创建函子
/**
* 使用new关键字创建函子
*/
class Container {
constructor(value) {
this._value = value//_value私有成员
}
//接收一个处理值的纯函数
map(fn) {
return new Container(fn(this._value))//返回新的函子
}
}
const functor1 = new Container(5).map(value => value + 1).map(value => value * 2);
console.log(functor1)//Container { _value: 12 }
代码示例2:使用含子的静态方法创建函子
/**
* 定义静态方法of(通过类名调用),封装new关键字,返回函子对象,避免使用new创建函子,
* 更深层的含义是of方法用来把值放到上下文中(把值放到容器中,使用map来处理值)
*/
class Container {
static of(value){//
return new Container(value);
}
constructor(value) {
this._value = value//_value私有成员
}
//接收一个处理值的纯函数
map(fn) {
return Container.of(fn(this._value))//返回新的函子
}
}
const functor1 = new Container(5).map(value => {
console.log(`value改变之前${value}`);
value=value + 1;
console.log(`value改变之后${value}`);
return value ;
}).map(value => value * 2);
console.log(functor1)//Container { _value: 12 }
MayBe
函子Either
函子map
契约的对象map
方法传递一个处理值的函数(纯函数),由这个函数对盒子中的值进行处理map
方法返回一个包含新值的盒子(函子)point free
:我们可以把数据处理的过程定义成与数据无关的合成运算,不需要用到代表数据的那个参数,只需要把简单的运算步骤合到一起,在使用这种模式之前我们需要定义一些基本的辅助函数。
代码示例:
const fp=require('lodash/fp');
//Hello World转成 hello_world
const fn=fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower);
console.log(fn('Hello World'))
//world wild web 转成 W. W. W
//使用空格分割成数据
//遍历数据,把数组的每一项都转换成大写,拿到数组每一项的第一个字符
// 把数据使用'. '合并成字符串
const fn=fp.flowRight(fp.join('. '),fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '));
console.log(fn('world wild web'))//W. W. W
Pointed
函子是实现了of
静态方法的函子。
MayBe
函子的作用是对外部空值情况做处理(控制副作用在允许的范围内)。
注意:MayBe
函子虽然可以处理空值的问题,但是如果多次调用map
方法的时候我们无法确定是那一次出现了空值
class MayBe {
static of(value) {
return new MayBe(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value));
}
// 判断value是否为空
isNothing() {
return this._value === null || this._value === undefined;
}
}
/**
* 验证输入null或者undefined
*/
// let res = MayBe.of('Hello World').map(value => value.toUpperCase());//MayBe { _value: 'HELLO WORLD' }
// let res = MayBe.of(null).map(value => value.toUpperCase());//MayBe { _value: null }
// let res = MayBe.of().map(value => value.toUpperCase());//MayBe { _value: null }
// console.log(res)
/**
*
*/
let res = MayBe.of('Hello World')
.map(value => value.toUpperCase())
.map(value=>null)
.map(value=>value.split(' '))
console.log(res)//MayBe { _value: null }
Either
函子的作用:异常可以让函子变得不纯,Either
函子可以用来做异常处理,使用Left
处理异常,使用Right
处理正常的情况。
代码示例:
/**
* 处理异常的函子
*/
class Left {
static of(value) {
return new Left(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
// return Left.of(fn(this._value));
return this;
}
}
/**
* 处理正常情况的函子
*/
class Right {
static of(value) {
return new Right(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return Right.of(fn(this._value));
}
}
// let rihgt1=Right.of(12)
// .map(value=>value+2)
// let left1=Left.of(12)
// .map(value=>value+2)
// console.log(rihgt1)
// console.log(left1)
function parseJSON(str) {
try {
return Right.of(JSON.parse(str))
} catch (e) {
return Left.of({ error: e.message })
}
}
// let res=parseJSON('{name:qian}');
// console.log(res);//Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
let res = parseJSON('{"name":"qian"}').map(value => value.name.toUpperCase());
console.log(res);//Right { _value: 'QIAN' }
IO
函子的特点:
IO
函子中的_value
是一个函数,因为函数是一等公民所以这里是把函数作为值来进行处理IO
函子可以把不纯的操作都存储到_value
,延迟执行这个不纯的操作(惰性执行)const fp=require('lodash/fp');
class IO{
static of(value){
return new IO(function(){
return value;
})
}
constructor(fn){
this._value=fn;
}
map(fn){//把fn和this._value组合成新的函数传递给IO函子
return new IO(fp.flowRight(fn,this._value));
}
}
/**
* 调用IO函子
*/
// process:进程对象
// execPath:当前node的执行路径
let res=IO.of(process).map(value=>value.execPath)
console.log(res)//IO { _value: [Function] }
console.log(res._value())//D:\Program Files\nodejs\node.exe
Monad
是可以变扁(解决函子嵌套的问题)的pointed
函子,如果一个函子同时具有join
和of
两个方法并遵守一些定律,那么这个函子就是Monad
函子。
folktale:一个标准的函数式编程库,和lodash、ramda的不同之处是没有提供很多的功能函数,只提供了一些函数式处理的操作,例如compose、curry、Task、MayBe
等。
Task 函子异步执行
代码示例:读取文件、解析内容
const {task}=require('folktale/concurrency/task');
const fs=require('fs');
const {find,split}=require('lodash/fp');
function readFile(fileName){
return task(resolver=>{
fs.readFile(fileName,'utf-8',(err,data)=>{
if(err){
resolver.reject(err);
}
resolver.resolve(data);
})
})
}
readFile('package.json')
.map(split('\n'))
.map(find(item=>item.includes('version')))
.run()//执行task
.listen({
onCancelled: () => { console.log('the task was cancelled') },
onRejected:err=>{
console.log(err);
},
onResolved:values=>{
console.log(values);// "version": "1.0.0",
}
})
JavaScript
作为浏览器脚本语言,主要用途是与用户互动,以及操作DOM
。想想看如果JavaScript
同时有两个线程,一个在某个DOM
节点上添加内容,另一个删除了这个节点,那这个时候浏览器应该以哪个线程为准?所以JavaScript
的用途决定了它是单线程的语言。
注意:单线程指的是在js
的执行环境中负责执行代码的线程只有一个。
单线程就意味着所有的任务都需要排队,前一个任务结束,才会执行下一个任务。如果前一个任务耗时很长,那么后面的任务就必须等待着,出现“假死”的情况。为了解决这一问题JavaScript
将任务的执行模式分成了两种:
EventLoop
就会从消息队列中取出第一个"回调函数"任务压入调用栈开启新一轮的任务执行。所谓"回调函数"(callback
),就是那些会被主线程挂起来的代码。异步任务必须指定回调函数,当主线程开始执行异步任务,就是执行对应的回调函数。
EventLoop
:负责监听调用栈和消息队列,一旦调用栈中的所有的任务都结束了,那EventLoop
就会从消息队列中取出第一个回调函数任务压入调用栈中开启新一轮的任务执行,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop
(事件循环)上图中,主线程运行的时候,产生堆(heap
)和栈(stack
),栈中的代码调用各种外部API
,它们在"消息队列"中加入各种事件(click,load
等)。只要栈中的代码执行完毕,主线程就会去读取"消息队列",依次执行那些事件所对应的回调函数。
promise
对象Promise
就是一个类,在执行的时候需要传递一个执行器,执行器会立即执行Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)、rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:
pending
变为fulfilled
pending
变为rejected
resolved
(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。resolve
和reject
函数的作用是:更改状态
resolve->fulfilled
reject->rejected
then
方法的作用是判断状态
then
方法是被定义在原型对象上的then
的成功回调函数有一个参数,表示成功之后的值then
失败回调 函数有一个参数,表示失败的原因promise
对象下面的then
方法是可以被调用多次的then
方法是可以被链式调用的
promise
对象的then
方法会返回一个全新的promise
对象then
方法就是在为上一个then
返回的promise
注册回调then
方法中回调函数返回的值会作为then
方法回调的参数promise
对象,那后面then
方法的回调会等待他的结束then
方法的参数可选promise.resolve()
将给定的值转换成promise
对象finally
的特点:
finally
都会执行一次finally
的后面链式调用then
方法可以拿到当前这个promise
对象的最终值catch()
的作用:处理当前promise
对象最终执行失败的情况,这样在then
中就不需要在调用失败回调函数,将会在catch
中捕获到。注意:catch
写法是针对于整个链式写法的错误捕获的,而then
第二个参数是针对于上一个返回Promise
的。
就是看谁在链式写法的前面,在前面的先捕获到错误,后面就没有错误可以捕获了,链式前面的优先级大,而且两者都不是break, 可以继续执行后续操作不受影响。
new Promise((resolve,reject)=>{
foo();
})
.then(()=>{
console.log(1)
},error=>{
console.log('我是then的第二个参数')
console.log(error)
}).catch(error=>{
console.log('我是catch')
console.log(error)
})
执行结果:new Promise((resolve,reject)=>{
foo();
})
.catch(error=>{
console.log('我是catch')
console.log(error)
})
.then(()=>{
console.log(1)
},error=>{
console.log('我是then的第二个参数')
console.log(error)
})
执行结果:Promise.all
可以将多个Promise
实例包装成一个新的Promise
对象,等待所有的任务完成才会结束。
reject
失败状态的值。Promise.all
获得的成功结果的数组里面的数据顺序和Promise.all
接收到的数组顺序是一致的promise.race
和第一个结束的任务一起结束,promise.race([p1, p2, p3])
里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
“消息队列”(回调队列)中的任务可以称为宏任务,宏任务执行过程中可以临时加上一些额外的需求,那这些额外的需求可以选择作为一个新的宏任务进到队列中排队,也可以作为当前任务的微任务,直接在当前任务结束之后立即执行,而不是到整个队伍的末尾在重新排队。
作为宏任务的有:
ajax
Node
中fs
可以进行异步的I/O
操作作为微任务的有:
promise
的回调函数MutationObserver
(创建并返回一个新的 MutationObserver
它会在指定的DOM
发生变化时被调用。)node
中的process.nextTick
(node
中实现的api
,把当前任务放到主栈最后执行,当主栈执行完,先执行nextTick
,再到等待队列中找)先同步,再异步(先微任务,再宏任务)
每次执行栈的同步任务执行完毕,就会去“消息队列”中取出完成的异步任务,队列中又分为微任务队列和宏任务队列,等到把微任务队列所有的微任务都执行完毕,才会从宏任务队列中取事件。等到把队列中的事件取出一个,放入执行栈执行完成,就算一次循环结束,之后event loop
还会继续循环,它会再去微任务队列执行所有的任务,然后再从宏任务队列里面取一个,如此反复循环。
整个 Generator
函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用 yield
语句注明。
next
方法的作用:是分阶段执行 Generator
函数。每次调用 next
方法,会返回一个对象,表示当前阶段的信息( value
属性和 done
属性)。
value
属性是 yield
语句后面表达式的值,表示当前阶段的值;done
属性是一个布尔值,表示 Generator
函数是否执行完毕,即是否还有下一个阶段。yield
语句的作用:暂停生成器函数的执行,直到外界再一次调用生成器对象的next
方法时从yield
的位置往下执行next
方法时传一个参数,那这个参数会作为yield
的返回值,即在yield
的左边可以接收到这个值。function* foo(x) {
console.log('start');
let res = yield 'foo';
console.log(res);
return res;
}
let g = foo(); //调用一个生成器函数并不会立即执行这个函数,而是得到一个生成器对象
//foo函数的函数体开始执行
console.log(g.next());//{value: "foo", done: false}
//向 Generator 函数体内输入数据
console.log(g.next('我是next的传值')); //{value: "我是next的传值", done: true}
上面的代码中,第一个 next
方法的 value
属性,返回表达式 ‘foo
’。第二个 next
方法带有参数我是next
的传值,这个参数可以传入 Generator
函数,作为上个阶段异步任务的返回结果,被函数体内的变量 res
接收。因此,这一步的 value
属性,返回的就是我是 next
的传值(变量res
的值)。
function* foo(x) {
console.log('start');
try{
let res = yield 'foo';
console.log(res);
}catch(e){
console.log(e);//Error: 异常
}
}
let g = foo(); //调用一个生成器函数并不会立即执行这个函数,而是得到一个生成器对象
let result = g.next(); //foo函数的函数体开始执行
console.log(result); //{value: "foo", done: false}
//抛出异常
g.throw(new Error('异常'))
async
必须声明的是一个function,没有await
时async
是同步函数async
函数会隐式地返回一个promise
,该promise
的reosolve
值就是函数return
的值。await
关键字只能用在async
定义的函数内,必须是直系(作用域链不能隔代)await
的本质是可以提供等同于”同步效果“的等待异步返回结果的语法糖。await
声明的Promise
异步返回,必须“等待”到有返回值的时候,即reslove
函数执行的返回值,代码才继续执行下去await
声明非promise
直接返回await
后面的值而不是等待其执行结果await
声明的是Promise
异步返回、定时器返回,还是普通值返回,then
都是最后执行的Async/Await
终止程序和一般的function
写法一样,想要中断的时候,直接 return
一个值就行,null
,false
等都是可以的async
函数会隐式地返回一个promise
let asyncFn=async()=>{
return '我是Promise';
//等同于 return Promise.resolve('我是Promise');
//等同于 return new Promise((resolve,reject)=>{ resolve('我是Promise') });
}
//`async`函数会隐式地返回一个`promise`,
//所以它具有then方法,也可以在then方法中获取到`async`函数的返回值
asyncFn().then((value)=>{
console.log(value);//我是Promise
})
await
的本质是可以提供等同于”同步效果“的等待异步返回结果的语法糖const demo = async () => {
let result = await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('我延迟了一秒')
}, 1000)
});
console.log('await程序还没执行完,我要先“等一会”');
return result;
}
// demo的返回当做Promise
demo().then(result => {
console.log('输出', result);
})
执行结果,等待1秒之后输出:
await
遇到定时器没有等待const demo = async () => {
let result = await setTimeout(() => {
console.log('我延迟了一秒');
}, 1000)
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result
}
demo().then(result => {
console.log('输出', result);
})
执行结果:
const demo = async () => {
let msg='msg';
let result = await msg;
console.log(result);
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result
}
demo().then(result => {
console.log('输出', result);
})
执行结果:
Async/Await
终止程序const demo = async () => {
let msg='msg';
let result = await msg;
console.log(result);
return;
//以下几种写法也是可以的
// return null;
// return false;
// return Promise.resolve('我退出了下面不进行了');
// return Promise.reject(new Error('拒绝'))
console.log('我由于上面的程序还没执行完,先不执行“等待一会”');
return result
}
demo().then(result => {
console.log('输出', result);
}).catch(error=>{
console.log('catch',error);
})