【ES6复习】异步函数

栏目: JavaScript · 发布时间: 5年前

内容简介:项目当中我们会遇到各种个样的异步操作,我们使用最多的方式就回调函数。那么在ES6中,给我们带来了什么样的解决方案呢?那当然是Promise和async/await了。Deferred延迟对象是在Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法可以注册两个回调函数,一个函数用于接收该 promise 的成功结果,第二个函数用于处理该 promise 失败的操作。

项目当中我们会遇到各种个样的异步操作,我们使用最多的方式就回调函数。那么在ES6中,给我们带来了什么样的解决方案呢?那当然是Promise和async/await了。

// 越来越深的回掉地狱
fs.readdir(source, function (err, files) {
    if (err) {
        console.log('Error finding files: ' + err)
    } else {
        files.forEach(function (filename, fileIndex) {
            console.log(filename)
            gm(source + filename).size(function (err, values) {
                if (err) {
                    console.log('Error identifying file size: ' + err)
                } else {
                    console.log(filename + ' : ' + values)
                    aspect = (values.width / values.height)
                    widths.forEach(function (width, widthIndex) {
                        height = Math.round(width / aspect)
                        console.log('resizing ' + filename + 'to ' + height + 'x' + height)
                        this.resize(width, height).write(dest + 'w' + width + '_' + filename, function (err) {
                            if (err) console.log('Error writing file: ' + err)
                        })
                    }.bind(this))
                }
            })
        })
    }
})

jQuery中使用Deferred

Deferred延迟对象是在 jQuery 1.5 中引入的,该对象提供了一系列的方法,可以将多个回调函数注册进一个回调队列里、调用回调队列,以及将同步或异步函数执行结果的成功还是失败传递给对应的处理函数。Deferred让我们的回调方法和异步函数变得更加可读了。

var p1 = $.post('/domainLogin'),
    p2 = $.post('/domainLogin'),
    p3 = $.post('/domainLogin');

// 串行
p1.done(p2).done(p3).done(function(){})
  .fail(function(){});

// 并行
$.when(p1,p2,p3).done(function(){}).fail(function(){});

// Deferred
var def = $.Deferred();
def.done(...).fail(...);
def.resolve();
//def.reject();

Promise

Promise 表示一个异步操作的最终结果,与之进行交互的方式主要是 then 方法,该方法可以注册两个回调函数,一个函数用于接收该 promise 的成功结果,第二个函数用于处理该 promise 失败的操作。

Promise的状态

  • pending:初始状态,也就是Promise刚被创建时的状态。
  • fulfilled/resolved:成功的操作。
  • rejected:失败的操作。

Promise的对象只能由pending变成resolved或者由pending变成rejected,且不可逆转。

let p = new Promise((resolve, reject) => {
    resolve('resolve p')
    // reject('reject p');
});

console.log(p);

p.then((res) => {
    console.log('then done ', res)
}, (res) => {
    console.log('then fail ', res)
});

【ES6复习】异步函数

Promise的方法

Promise.prototype.then()

then 方法是处理Promise状态变换的回掉函数,第一个参数是 resolved 状态时的回调函数,第二个参数时 rejected 状态时的回调函数,其中第二个参数可以忽略。

不管是 resolved 还是 rejected ,都会返回一个新的Promise实例,也就是说 then 后面可以继续链式的调用 then

Promise
    .resolve()
    .then(()=>{
        console.log('resolve');
        // 这里 reject 会触发第二个then的rejected函数
        return Promise.reject();
    }, console.log.bind(null, 'reject'))
    .then(...);

Promise.prototype.catch()

catch 方法等于 .then(null, rejected)

rejected 的状态都会进入到catch中,包含代码异常。

如果整个Promise中有异常没有被catch,就会抛出一个错误unhandled rejection。

Promise.reject();
Promise.reject().catch(()=>{conso.log('catch')});
// Uncaught (in promise)

Promise.prototype.finally()

和他名字一样,无论Promise的状态是什么,都会执行的回调函数。:warning:注意的是它始终返回的是原来Promise的状态。

finally 等同于:

promise
    .then(
        result => {
            return result;
        },
        error => {
            // 注意是抛出异常,也就是reject状态,而不是默认的resolve
            throw error;
        }
    );

Promise.all() / Promise.race()

他们都接受一个数组作为参数,这个数组当中包含多个Promise实例。该函数会把这些Promise包装成一个新的Promise实例。

不同的是:

Promise.all:需要等待所有Promise状态都变成 resolved ,该实例的状态才会是 resolved ,且他的结果是一个数组;如果其中有一个Promise的状态变成 rejected ,该实例的状态立即会变成 rejected ,且reject原因就是该Promise失败的原因。

Promise.race:只要有一个实例状态变化之后,整个Promise实例都变成该状态。

Promise
    .all([1,Promise.resolve(2), Promise.resolve(3)])
    .then(console.log)
    .catch(console.error);

Promise.race([
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 1000);
        // 设置3000秒的结果呢?
    }),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(2);
        }, 2000);
    })
]).then(console.log)
    .catch(console.error);

Promise.resolve()

接受一个参数,可以把它装成Promise对象

1、当参数为Promise实例:

直接返回该实例。

2、当参数为一个thenable对象(包含then方法)

let thenable = {
  then: (resolve, reject)=>{
    resolve();
  }
}

Promise.resolve会把它包装成一个Promise对象,并执行它的then方法,等同于:

Promise.resolve(thenable);
// 等同于
new Promise(thenable);

3、其它类型(基本类型/对象/函数/…)

直接返回一个状态为 resolved 的Promise实例,并把该参数作为值传递给 then

new Promise((resolve, reject)=>{
  resolve('resolve 1');
}).then((result)=>{
  console.log(result);
}).catch((e)=>{
  console.error(e);
}).finally(()=>{
  console.log('finally');
})

Promise.reject()

改方法返回一个状态为 rejected 的Promise实例,可以接受一个参数,这个参数或作为 rejected 的原因。

Promise.reject('err')
.catch(e=>console.log(e)); // err

Generator

暂无

async/await

个人觉得Generator函数的语法晦涩难懂,且使用起来不太方便,所以就不做他们之间的对比了。

接下来我们就积极的拥抱 async/await 吧。

async 函数内部可以包含多个异步操作,通过 await 可以让异步操作像同步代码一样书写,并且 async 返回的还是一个Promise实例,可以用promise方法继续操作。注意, await 命令只能在 async 函数中使用。

基本用法

async function doAsync() {
    console.log('before');
    const result = await fetch('https://api.github.com/');
    console.log('after');
}

doAsync();

await命令的功能和Promise.resolve类似,如果await后面是一个promise实例或者thenable对象,就会返回他们的结果,如果后面是其他值,就直接返回该值。

错误处理

如果 await 后面的promise被 rejectedasync 函数就会抛出异常,可以使用 try...catch 处理异常。由于 async 函数返回的是promise对象,所以也可以在 async 后面使用 catch 方法捕获异常。

async function doAsync() {
    console.log('before');
    // try...catch捕获异常
    try {
        const result = await new Promise(() => {
            throw 'error'
        });
    } catch (e) {
        console.log('err:', e)
    }

    console.log('after');
}

doAsync();

异步函数问题分析

与setTimeout的的执行顺序

熟悉setTimeout和setInterval都知道,他并不是在指定时间内执行,而是在这个事件内把回调函数放到到事件队列中,具体知识点可以了解事件循环相关资料。根据事件循环中的定义,Promise是在 本次循环的微任务 中,而setTimeout是在 次轮循环 当中,所以Promise的then会优先与setTimeout执行。

console.log('a');

setTimeout(() => {
    console.log('b')
});

new Promise((resolve) => {
    console.log('c');
    resolve()
}).then(() => {
    console.log('d')
});

console.log('e');

// a c e d b

回调函数改写成async/await的注意点

虽然这不是async/await的问题,但这确实能在不经意间写出低效的代码。

案例1:成功的解决了回掉地狱的问题,并且效果是等晓得

案例2:改写完后,原来的 a b 并行执行顺序被改写成 a b c d 串行执行,整个函数的执行事件肯定大大增加。我们用 改写2 方法能够达到同样的效果,但是代码却变得复杂了,虽然这个案例本身有不合理的地方,但是我们在改写成 async/await 还是需要多注意实际的效果是否和我们预期相符。

// 四个函数 a b c d

// 案例1
a(()=>{
  b();
})
// 案例1 改写
await a();
await b();

// 案例2
a(()=>{
  b();
})
c(()=>{
  d();
})
// 案例2 改写
await a();
await b();
await c();
await d();
// 案例2 改写2
(async () => {
  await a();
  b();
})();
(async () => {
  await c();
  d();
})();

async函数和Promise执行先后顺序

前段时间在知乎上看到讨论async函数的文章,有评论称“前端真TM事儿多,都已经是async函数了还要解决谁先执行”,:joy:虽然很有道理,但是孔乙己不也很自豪的炫耀“回”字的四种写法么。

分析下面的demo:

1、同步代码执行部分 a c b e i 没有什么异议

2、await后的异步代码的输出顺序,我们期望的可能是 d f g h ,事实并非如此,那么究竟是为什么呢?

原因在于ECMAScript规范导致目前每个await都会创建额外的两个promise对象(即使他本身已经是个promise对象),这就导致了await后面的内容至少需要在第三次微任务队列执行。

据介绍,优化后的变更已经提交,新版本将会判断await后面是不是promise对象,如果为promise对象就会直接返回该对象,从而减少额外的promise创建,我们在canary版 Chrome/73.0.3652.0中执行下面代码,测试结果为 a c b e i d f g h

// 结果来至:Chrome/71.0.3578.98  
console.log('a');

async function async01() {
    console.log('b')
}

(async () => {
    console.log('c');
    await async01();            // 结果: a c b e i    f g d h
    // await Promise.resolve(); // 结果: a c b e i    f g d h
    // await 123;               // 结果: a c e i      d f g h
    console.log('d');
})();

new Promise((resolve) => {
    console.log('e');
    resolve();
}).then(() => {
    console.log('f');
}).then(() => {
    console.log('g');
}).then(() => {
    console.log('h');
});

console.log('i');

以上所述就是小编给大家介绍的《【ES6复习】异步函数》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

App研发录:架构设计、Crash分析和竞品技术分析

App研发录:架构设计、Crash分析和竞品技术分析

包建强 / 机械工业出版社 / 2015-10-21 / CNY 59.00

本书是作者多年App开发的经验总结,从App架构的角度,重点总结了Android应用开发中常见的实用技巧和疑难问题解决方法,为打造高质量App提供有价值的实践指导,迅速提升应用开发能力和解决疑难问题的能力。本书涉及的问题有:Android基础建设、网络底层框架设计、缓存、网络流量优化、制定编程规范、模块化拆分、Crash异常的捕获与分析、持续集成、代码混淆、App竞品技术分析、项目管理和团队建设等......一起来看看 《App研发录:架构设计、Crash分析和竞品技术分析》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换