1. 回调函数
回调(callback)是一个函数被作为一个参数传递到另一个函数里,在那个函数执行完后再执行。
假定有两个函数f1和f2,f2等待f1的执行结果,f1()–>f2();如果f1很耗时,可以改写f1,把f2(箭头函数)写成f1的回调函数:
function f1(callback){ setTimeout(() => { let name = '小明' console.log('大家好') // f1的任务代码 callback(name) }, 100); } f1((name)=>{ console.log('我是'+name) }) // 大家好 // 我是小明
优点:简单、方便、实用。
缺点:易形成回调函数地狱。如果我们只有一个异步操作,用回调函数来处理是完全没有任何问题的。。但是如果我们要嵌套很多个回调函数,问题就很大了,因为多个异步操作形成了强耦合,代码将乱作一团,无法管理。这种情况被称为"回调函数地狱"(callback hell)。
getData('XXX1', () => { // callback 函数体 getData('XXX2', () => { // callback 函数体 getData('XXX3', () => { // callback 函数体 getData('XXX4', () => { // callback 函数体 getData('XXX5', () => { // callback 函数体 }) }) }) }) })
2. Promise 对象(推荐)
含义: Promise是异步编程的一种解决方案,
优点: Promise是链式编程,有效的解决了令人头痛的回调地狱问题,Promise的结果有成功和失败两种状态,只有异步操作的结果,可以决定当前是哪一种状态,外界的任何操作都无法改变这个状态。
2.1 常用API
resolve 返回异步操作成功的结果
reject 返回异步操作失败的结果
then 执行 Promise 状态是成功的操作
catch 执行 Promise 状态是失败的操作
finally 不管 Promise 状态是成功或失败都执行的操作
//ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。 const p = new Promise(function(resolve,reject){ if(success){ resolve('成功的结果') }else{ reject('失败的结果') } }) p.then(function (res) { // 接收resolve传来的数据,做些什么 },function (err) { // 接收reject传来的数据,做些什么 }) p.catch(function (err) { // 接收reject传来的数据或者捕捉到then()中的运行报错时,做些什么 }) p.finally(function(){ // 不管什么状态都执行 })
const p = new Promise((resolve, reject) =>{ setTimeout(() => { let name = '小明' resolve(name) }, 100); }); p.then((res) => { console.log(res) })
2.2 Promise.all
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3])
p 的状态由 p1、p2、p3 决定,分成两种情况。
(1)只有 p1、p2、p3 的状态都变成 fulfilled,p 的状态才会变成 fulfilled,此时 p1、p2、p3 的返回值组成一个数组,传递给 p 的回调函数(then)。
(2)只要 p1、p2、p3 之中有一个被 rejected,p 的状态就变成 rejected,此时第一个被 reject 的实例的返回值,会传递给 p 的回调函数(catch)。
优点:
promise.all 将多个 promise 放在一起处理,能简化回调的处理(不然需要调很多次,不科学),一个 then 回调就能拿到所有数据,对其进行处理,也能用一个 catch 回调捕获所有的异常。
console.time('time'); function test1(){ const p1 = new Promise((resolve, reject) => { console.log('begin p1'); setTimeout(() => { resolve('end p1'); },400); }); return p1; } function test2(){ const p2 = new Promise((resolve, reject) => { console.log('begin p2'); setTimeout(() => { resolve('end p2'); },300); }); console.log(p2); return p2; } function load1(){ setTimeout(() => { console.log('setTimeout'); Promise.all([test1(), test2()]).then((res) => { console.log('load1',res); console.timeEnd('time'); }).catch(err => { console.log(err) }); },100); } load1(); console.log('mark');
console.time('time'); function test1(){ const p1 = new Promise((resolve, reject) => { console.log('begin p1'); setTimeout(() => { resolve('end p1'); },400); }); return p1; } function test2(){ const p2 = new Promise((resolve, reject) => { console.log('begin p2'); setTimeout(() => { resolve('end p2'); },300); }); console.log(p2); return p2; } async function load2(){ const r1 = await test1(); const r2 = await test2(); console.log('load2',r1,r2); console.timeEnd('time'); } load2(); console.log('mark');
3. 事件监听
采用事件驱动模式,任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:on,bind,listen,addEventListener,observe
举例,为f1绑定一个事件(jquery写法):f1.on(‘done’,f2);即当f1发生done事件,就执行f2。
function f1(){ settimeout(function(){ // f1的任务代码 f1.trigger('done'); // 执行完成后,立即触发done事件,从而开始执行f2 },1000); }
优点:易理解,可绑定多个事件,每一个事件可指定多个回调函数,可以去耦合,有利于实现模块化
缺点:整个程序都要变成事件驱动型,运行流程会变得不清晰
4. Generator 函数
含义: Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。
基本用法:
function* helloGenerator() { yield 'hello'; yield 'Generator'; return 'over'; } let hw = helloGenerator(); hw.next() // {value:"hello",done:false} hw.next() // {value:"Generator",done:false} hw.next() // {value:"over",done:true} hw.next() // {value:undfined,done:true}
特征
function关键字与函数名之间有一个星号;
函数体内部使用yield表达式,定义不同的内部状态;
通过next方法获取每段状态的返回结果,上面分成4次执行了Gennrator函数。
第一次,获取第一个yield函数的返回结果并暂停,done:false,代表函数内的状态还没有执行结束;
第二次,同理;
第三次,获取return 的返回结果,done:true表示函数内的状态已经执行完毕;
第四次,函数内已经没有可以执行的状态,所有返回undfined,同时告诉函数内的状态已经执行完毕;
如果函数内没有return,在第三次时返回undfined,done:true表示函数内的状态已经执行完毕;
5. async 函数 (推荐)
含义: async 函数是在ES2017 标准中引入的,async 使得异步操作变得更加方便,其实他就是Generator 函数的语法糖
基本用法:
function get1(){ return new Promise((resolve,reject)=>{ setTimeout(()=>{ resolve(1) },2000) }) } async function getSet(){ const n = await get1() //const n = await '111' return n } getSet().then(console.log)
说明:
await后面是一个Promise对象,如get1 return出去的Promise实例;
如果不是 Promise 对象,就直接返回对应的值,如直接返回’111’。
若Promise 对象, 并且其以值 x 被 fulfilled, 则返回值为 x.
Promise 对象, 并且其以异常 e 被 rejected, 则抛出异常 e
async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态 改变;
如果任何一个await语句后面的 Promise 对象变为reject状态或遇到return,那么整个async函数都会中断执行。
await 在等待 Promise 对象时会导致 async function 暂停执行, 一直到 Promise 对象决议之后才会 async function 继续执行;
如果我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await放在 try…catch结构里面,这样不管这个异步操作是否成功,第二个await都会执行。
async function f() { try { await Promise.reject('出错了'); } catch(e) { } return await Promise.resolve('hello world'); } f().then(v => console.log(v))
function getData() { return new Promise((resolve, reject) => { setTimeout(() => { let name = '明明' resolve(name) }, 100); }); } async function test() { let newData = await getData() console.log(newData) } test() // 明明
优点: 相比Generator函数,async函数有如下四点改进
内置执行器: Generator 函数的执行必须靠next()进行每一次的模块执行,async自带执行器,只需要和普通函数一样调用即可执行
更好的语义:async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。
返回值是Promise: async函数的返回值是 Promise 对象,可以用then方法指定下一步的操作;
而且async函数完全可以看做多个异步函数的操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖,即Promise.all()的用法 ;
多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
//此处省略getFoo(), getBar()两个函数 // 写法一 async function getSet(){ let [foo, bar] = await Promise.all([getFoo(), getBar()]); return [foo, bar] } // 写法二 async function getSet(){ let fooPromise = getFoo(); let barPromise = getBar(); let foo = await fooPromise; let bar = await barPromise; return [foo, bar] }