1、回调函数:
回调函数就是一个参数,将这个函数作为参数传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数。这个过程就叫做回调。
function a(callback){alert("这是first函数a");var x =1;var y=2;return callback(x,y);}function b(x,y){alert("这是回调函数b");return x+y;}$(function(){var result = a(b);alert("result = "+ result);});
//这是first函数a,这是回调函数bresult = 3//这里函数首先执行了first函数a,之后调用了回调函数b,最后返回函数a的返回值。
回调种类:同步回调、异步回调
回调可以是同步的
function a(fn) {fn();
};a(funtion() {console.log('I am callback, but not async'); // step1
});
console.log(end);// step2
回调缺点:
- 高耦合,维护困难,回调地狱;
- 每个任务只能指定一个回调函数;
- 如果几个异步操作之间并没有顺序之分,同样也要等待上一个操作执行结束再进行下一个操作。
2、promise
ES6给我们提供了一个原生的构造函数Promise,Promise代表了一个异步操作,可以将异步对象和回调函数脱离开来,通过.then方法在这个异步操作上绑定回调函数,Promise可以让我们通过链式调用的方法去解决回调嵌套的问题,而且由于promise.all这样的方法存在,可以让同时执行多个操作变得简单。
promise对象存在三种状态:
- Fulfilled:成功状态
- Rejected:失败状态
- Pending:既不是成功也不是失败状态,可以理解为进行中状态
Promise的缺点:
- 当处于未完成状态时,无法确定目前处于哪一阶段。
- 如果不设置回调函数,Promise内部的错误不会反映到外部。(resolve就是回调函数)
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。
promise的all,数组中三个数,其中两个正常,一个会报错,怎么让正常的两个返回
由于
Promise.all(request).then(…).catch(…)
会在所有request
都resolve
时才会进then
方法,并且把所
有结果以一个数组返回,只要有一个失败,就会进catch
。而如果在单个请求中定义了catch
方法,那么就
不会进Promise.all
的catch
方法。因此,可以在单个的catch
中对失败的promise
请求做处理,可以使
成功的请求正常返回。
①Promise then reject了,后面的catch会执行吗?(会)②Promise Catch后面的then还是会执行?(会)
catch()是then()的语法糖。then()返回的是一个promise。 因为then()和catch()又返回了一个promise,因此,后续调用可以串联起来。then()的两个参数函数中返回的值,会自动包装成一个已resolved的promise。
function test(res) {return Promise.resolve(res).then(res => {console.log(res += '!');return res;}).then(res => {console.log(res += '!');return Promise.reject("end"); //此处返回了一个新的promise}).catch(res => {console.log(res);return res; //此处也返回了一个新的resolved的promise}).then(res => {console.log(res += '!'); //肯定会执行了});
}
test('hello');
function test(res) {//创建一个rejected状态的Promisereturn Promise.reject(res).then(res => { //此处不会执行!!!console.log(res += '!');return res;}).catch(res => {console.log(res);return res; //catch执行完后,返回了一个新的Fulfilled的状态的Promise,
//等同于return Promise.resolve(res);所以后面的then会执行,而catch就不会执行了。}).then(res => {console.log(res += '!'); }) .catch(res => {console.log(res+"?");return res; });
}
test('hello').then((res) =>{//test方法中将错误统一栏截处理了,可以不返回内容,//然后此处判断res来确定要不要执行!!
});
可以将非函数参数传递给Promise.then()而不会导致错误?
将非函数参数传递给Promise.then()不会导致错误,then(不是回调函数)也不会进入微任务队列,直接执行(同步)
3、 generator原理
Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象,可以依次遍历 Generator 函数内部的每一个状态,但是只有调用next
方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield
表达式就是暂停标志。
必须调用遍历器对象的next
方法,使得指针移向下一个状态。也就是说,每次调用next
方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield
表达式(或return
语句,如果没有return
语句,就执行到函数结束)为止。yield
表达式是暂停执行的标记,而next
方法可以恢复执行。
function* gen(x) {console.log('start')const y = yield x * 2return y
}const g = gen(1)
g.next() // start { value: 2, done: false }
g.next(4) // { value: 4, done: true }
- gen()? 不会立即执行,而是一上来就暂停,返回一个 ?Iterator ?对象(具体可以参考 Iterator遍历器)
- 每次? g.next() ?都会打破暂停状态去执行,直到遇到下一个? yield ?或者 ?return?
- 遇到? yield ?时,会执行 ?yeild? 后面的表达式,并返回执行之后的值,然后再次进入暂停状态,此时? done: false?。
- ?next? 函数可以接受参数,作为上个阶段异步任务的返回结果,被函数体内的变量接收
- 遇到? return ?时,会返回值,执行结束,即 ?done: true?
- 每次? g.next() ?的返回值永远都是 ?{value: ... , done: ...} ?的形式
如果没有再遇到新的yield
表达式,就一直运行到函数结束,直到return
语句为止,并将return
语句后面的表达式的值,作为返回的对象的value
属性值。
如果该函数没有return
语句,则返回的对象的value
属性值为undefined
。
Generator函数暂停恢复执行原理:协程、执行器(Generator 是一个异步操作的容器。它的自动执行需要一种机制,当异步操作有了结果,能够自动交回执行权。)
4、async和await
await后面的表达式是从右到左运行的。await针对所跟不同表达式的处理方式:
- Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
- 非 Promise 对象:直接返回对应的值。
async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。为什么会暂停执行,原理是什么?
async/await
async 是Generator函数的语法糖,并对Generator函数进行了改进,是对 yield 的简单封装
async函数对 Generator 函数的改进,体现在以下四点:
-
内置执行器
。Generator 函数的执行必须依靠执行器,而 async 函数自带执行器,无需手动执行 next() 方法。 -
更好的语义
。async和await,比起星号和yield,语义更清楚了。async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 -
更广的适用性
。co模块约定,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。 -
返回值是 Promise
。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用。
重点是自带了执行器,相当于把我们要额外做的(写执行器/依赖co模块)都封装了在内部
await后面的函数执行完毕时,await会产生一个微任务(Promise.then是微任务)。但是我们要注意这个微任务产生的时机,它是执行完await之后,直接跳出async函数,执行其他代码。其他代码执行完毕后,再回到async函数去执行剩下的代码,然后把await后面的代码注册到微任务队列当中
五、事件循环机制,两者有区别:
浏览器环境下:当主代码微任务执行完毕,开始执行宏任务,这时宏任务中有微任务时,会马上执行微任务再执行其他的宏任务。
nodejs:当宏任务中有微任务时,会先将微任务放到微任务队列中,执行完宏任务再执行微任务。