ES6异步编程

297 阅读4分钟

这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战

同步和异步

同步

同步就是在执行某段代码时,在该代码没有得到返回结果之前,其他代码暂时无法执行;只有执行完成拿到返回结果之后才能执行其他代码。即为在此段代码执行完未返回结果之前会阻塞后面代码的执行

异步

异步就是在执行某段代码时,不会立刻得到返回结果。而是在异步调用发出后,一般通过回调函数处理这个调用之后拿到结果。异步调用发出后不会阻塞后面的代码执行

为什么需要异步

JS是单线程的,如果都是同步代码执行,会阻塞后面代码执行;而使用异步就不会阻塞代码执行,不需要等到异步代码执行结果,就可以继续执行异步任务后面的代码逻辑。

Promise

Promise的实现方式在一定程度上解决了回调地狱的问题,但并未完全解决

function read(url) {
    return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {  if(err) reject(err); resolve(data);  });
    });
}
read(A).then(data => {
    return read(B);
}).then(data => {
    return read(C);
}).then(data => {
    return read(D);
}).catch(reason => {
    console.log(reason);
});

针对回调地狱的情况,有所改进,可读性也有一定的提升。可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

但是Promise的链式调用在操作过多的情况下并没有从根本上解决回调地狱的问题,只是换了一种写法而已。

Generator

最大的特点就是交出了函数的执行权,可以将Generator函数看作异步任务的容器,需要暂停的地方都是用yield语法进行标注。

Genertor函数最后返回的是迭代器。

function* gen() {
    let a = yield 111;
    console.log(a);
    let b = yield 222;
    console.log(b);
    let c = yield 333;
    console.log(c);
    let d = yield 444;
    console.log(d);
}
let t = gen();
t.next(1); //第一次调用next函数时,传递的参数无效,故无打印结果   {value: 111, done: false}
t.next(2); // a输出2;     {value: 222, done: false}
t.next(3); // b输出3;     {value: 333, done: false}
t.next(4); // c输出4;     {value: 444, done: false}
t.next(5); // d输出5;     {value: undefined, done: true}

async/await

ES7提出的异步解决方案,是Generator函数的语法糖。

优点是代码结构清晰(不像Promise那样有很多then方法链),可以处理回调地狱问题,使得异步代码看起来像是同步代码

function testWait() {
    return new Promise((resolve,reject)=>{
        setTimeout(function(){ console.log("testWait"); resolve(); }, 1000);
    })
}
async function testAwaitUse(){
    await testWait()
    console.log("hello");
    return 123;   // 输出顺序:testWait,hello  第十行如果不使用await输出顺序:hello , testWait
}
console.log(testAwaitUse());

在正常的执行顺序下,testWait 这个函数由于使用的是 setTimeout 的定时器,回调会在一秒之后执行, 但是由于执行到这里采用了 await 关键词,testAwaitUse 函数在执行的过程中需要等待 testWait 函数执行完成之后,再执行打印 hello 的操作。但是如果去掉 await ,打印结果的顺序就会变化。

Promise方法

Promise.all()

针对链式调用操作很多的情况,使用Promise.all()方法会更好

// 通过 Promise.all 可以实现多个异步并行执行,同一时刻获取最终结果的问题
Promise.all([read(A), read(B), read(C)]).then(data => {
    console.log(data);
}).catch(err => 
    console.log(err)
);

allSettled方法

语法

Promise.allSettled(iterable)

参数

一个Promsie数组

返回值

返回的是包含每个Promise返回值的数组

描述

执行完不会失败。执行完毕后可以拿到所有Promise的状态,不管状态是成功还是失败

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const allSettledPromise = Promise.allSettled([resolved, rejected]);
allSettledPromise.then(function (results) {
  console.log(results);
});
// 返回结果:
// [
//    { status: 'fulfilled', value: 2 },
//    { status: 'rejected', reason: -1 }
// ]

any方法

语法

Promise.any(iterable)

参数

一个可迭代的对象,例如 Array。

描述

any 方法返回一个 Promise,只要参数 Promise 实例有一个变成fulfilled 状态,最后 any 返回的实例就会变成 fulfilled 状态;如果所有参数 Promise 实例都变成rejected 状态,包装实例就才会变成rejected 状态。

const resolved = Promise.resolve(2);
const rejected = Promise.reject(-1);
const anyPromise = Promise.any([resolved, rejected]);
anyPromise.then(function (results) {
  console.log(results);  // 返回结果: 2
});

有一个状态成功就返回结果

race方法

语法:

Promise.race(iterable)

参数:

一个可迭代的对象,例如 Array。

描述:

race 方法返回一个 Promise,只要参数的 Promise 之中有一个实例率先改变状态,则 race 方法的返回状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给 race 方法的回调函数。

对图片的加载,很适合用race解决。

//请求某个图片资源

function requestImg(){
  var p = new Promise(function(resolve, reject){
    var img = new Image();
    img.onload = function(){ resolve(img); }
    img.src = 'http://www.baidu.com/img/flexible/logo/pc/result.png';
  });
  return p;
}

//延时函数,用于给请求计时
function timeout(){
  var p = new Promise(function(resolve, reject){
    setTimeout(function(){ reject('图片请求超时'); }, 5000);
  });
  return p;
}

Promise.race([requestImg(), timeout()])
.then(function(results){
  console.log(results);
})
.catch(function(reason){
  console.log(reason);
});

重点在于哪个实例先改变状态,返回的状态就是对应实例的状态