由于JS运行环境是单线程的,即一次只能完成一个任务,所以多任务时需要排队。异步可以理解为改变执行顺序的操作,异步任务必须在同步任务执行结束之后,从任务队列中依次取出执行。
js常见的异步方法有四种:
 
1,回调函数callback
函数B作为函数A的入参,且函数A执行了函数B,此时我们把函数A叫做回调函数。(ajax、setTimeout、dom事件回调等都是回调函数)
例如:
function A(callback) {
    console.log("A");
    callback();//函数A执行了函数B
}
function B() {
    console.log("B");
}
A(B);//函数B作为函数A的入参
function A(callback){
    console.log("A");
    callback("param B");//函数A执行了函数"B",给函数"B"入参"param B"
}
A((val) => { //函数"B"作为函数A的入参,"B"有一个入参val
    console.log(val);//输出"param B"
})
  缺点:高耦合,结构混乱,回调函数不能使用 try catch 捕获错误,不能直接 return
 
2,Promise
当回调的层级较少时,回调函数可以满足我们的需求,但是当层级越来越多,回调越来越多,例如吃饭,要吃饭就要先做饭,要做饭就要先淘米,要淘米就要先买米,要买米就要去菜场(或者APP),去菜场就要坐车,坐车就要准备公交卡……如此形成回调地狱!(下一篇再说回调地狱吧)
Promise很好地解决了回调地狱的问题,它包含三种状态:pending、fulfilled(resolved)、rejected。pending是promise的初始状态,resolved表示执行完成且成功的状态,rejected表示执行完成且失败的状态。三个状态不可逆转
Promise本身是同步,then的内容是异步:
let p = new Promise((resolve,reject) => {
    console.log("promise本身是同步");
    resolve("then是异步");
}).then((res) => {
    console.log(res);
})
console.log("想不到吧");
输出:
then接收两个回调函数,一个表示成功,一个表示失败:
let p = new Promise((resolve,reject) => {
    console.log("promise本身是同步");
    reject("then是异步");
}).then((res) => {
    console.log(res);
},(err) => {
    console.log(err);
})
console.log("想不到吧");
第二个回调也可以使用catch
let p = new Promise((resolve,reject) => {
    console.log("promise本身是同步");
    reject("catch是异步");
}).then((res) => {
    console.log(res);
}).catch((err) => {
    console.log(err);
})
console.log("想不到吧")
promise解决回调地狱的问题的方法是链式调用:
function testP(val) {
    return new Promise((resolve, reject) => {
       resolve(val);
    });
}
testP("0").then(res1 => {
    console.log(res1); //输出0
    return testP("1");
}).then(res2 => {
    console.log(res2); //输出1
    return testP("2");
}).then(res3 => {
    console.log(res3); //输出2
    return testP("3");
}).catch(err => {
    console.log(err);
});
首先testP("0")创建了一个promise,进入pending状态,当resove后进入fulfilled状态第一个then中输出0并返回一个promise,进入pending状态,当resove后进入fulfilled状态,第二个then中输出1并返回一个promise......以此类推。
我们修改函数testP:
function testP(filename){
  return new Promise((resolve,reject) => {
      fs.readFile(filename, (err, data) => {
          if(err) {
              reject(err);
          }else {
              resolve(data);
          }
      }) 
  })
}
testP(url).then(res1 => {
    return testP(url1);
}).then(res2 => {
    return testP(url2);
}).then(res3 => {
    return testP(url3);
}).catch(err => {
    console.log(err);
});
testP返回一个promise,then中传入回调函数,上文我们得出结论promise本身是同步,then是异步,then延迟传入回调函数,此为回调函数延迟绑定。
继续改写testP:
function testP(filename){
  return new Promise((resolve,reject) => {
      fs.readFile(filename, (err, data) => {
          if(err) {
              reject(err);
          }else {
              resolve(data);
          }
      }) 
  })
}
let step1 = testP(url).then(res => {
    return testP(url1);
}).catch((err) => {
    console.log(err);
})
let step2 = step1.then((res) => {
    return testP(url2)
}).catch((err) => {
    console.log(err);
})
let step3 = step2.then((res) => {
    return testP(url3)
}).catch((err) => {
    console.log(err);
})
step3.then((res) => {}).catch((err) => {
    console.log(err);
})
将每一个then拆开,step1/step2/step3都是上文链式调用的内部返回,在step1/step2/step3之后都可以继续完成之后的链式调用,此为返回值穿透。
在step1/step2/step3中都进行了catch捕获异常(即rejected状态),链式调用时,rejected状态的异常信息会一直往后传递,直到被catch接收到,所以不用频繁的catch,此为错误冒泡。
缺点:无法取消 Promise。
 
3,Generator函数(ES6)
Generator 是一个可以暂停执行(分段执行)的函数,函数名前面要加星号,是一个状态机,封装了多个内部状态。
function *myGenerator() {
    yield 'hello';
    yield 'world';
    yield 'wawawa';
    return 'amazing';
}
let mg = myGenerator();
调用myGenerator()但并不会执行,它返回一个指向函数内部的指针对象。调用对象的next()方法使指针指向下一个,每次next()会从上一次暂停的地方继续执行,直到遇到yield或者return,Generator分段执行,yield是标记暂停的地方,next表示恢复执行,每次next返回的是一个对象,包含value和done,value的值是yield表达式后面的值,done表示是否执行完毕。
输出:
 缺点:手动迭代。
 
4,async/await(ES7)
基于Promise实现,使异步代码看起来更像同步代码。
async修饰符加在函数前面,返回一个promise,可以使用then添加回调函数。
await后跟着一个promise或者一个原始类型的值(会自动转成立即 resolved 的 Promise 对象),等待resolve的结果。任何一个await后的Promise发生reject,整个aysnc都会中断,需要try{}catch(err){}来捕获错误。
async function mysw(){
    return 1;
}
mysw();//返回一个promise,其中PromiseResult是1
async function mysw(){
    return new Promise((resolve,reject) => {
        resolve(1)
    });
}
mysw(); //同上
async function mysw(){
    await 1;
}
mysw(); //返回一个promise,其中PromiseResult是undefined
async function mysw(){
    let a = await 1; //1会转成resolve(1)的promise
    console.log(a);
}
mysw(); //输出1,返回PromiseResult是1的promise
async function mysw(){
    let a = await new Promise((resolve) => { //同上
        resolve(1)
    });
    console.log(a);
}
mysw();//同上
async function mysw(){
    let a = await new Promise((resolve) => {
        resolve(1)
    });
    console.log(a)
    return 1;
}
mysw(); //输出1,返回一个PromiseResult值为1的promise
async function mysw(){
    let a = await new Promise((resolve) => {
        resolve("a");
    });
    console.log(a);
    return 1;
}
mysw().then((res) => {
    console.log(res);//输出a,1,返回一个PromiseResult为undefined的promise
});
async function mysw(){
    try {
        let a = await new Promise((resolve) => {
        resolve(x);
    });
        console.log(a);
    } catch(err) {
        console.log(err); 
    }
    return 1;
}
mysw();//ReferenceError: x is not defined

看起来async/await和Generator用法相似,把星号换成async,把yield换成await,它们区别在于:


1,async函数自带执行器

2,语义清楚

3,async函数的返回值是 Promise 对象,Generator 函数的返回值是 Iterator 对象

4,yield命令后面只能是 Thunk 函数或 Promise 对象,而async函数的await命令后面,可以是 Promise 对象和原始类型的值

await语句后面的内容需要等待await的内容执行完才能执行(宏任务除外),可以把await当成是then的语法糖,await之后的内容就相当于then里的回调函数,是异步,根据先微后宏的顺序继续执行(事件循环在后面讲哈)

posted on 2022-09-07 15:04  Zoie_ting  阅读(8651)  评论(0编辑  收藏  举报