ECMASCRIPT6的异步编程解决方案

    xiaoxiao2021-03-25  80

    ES6提供了三种异步解决:

    PromiseGeneratorAsync

    Promise

    Promise最早由一些社区提出和实现(jquery.deffered,q,angular),ES6将其写进了语言标准,统一了用法,原生提供了Promise对象。 Promise代表一个异步操作,有三种状态:

    Pending: 进行中 Resolved: 已完成 Rejected:已失败

    两个特点:

    对象的状态不受外界影响;一旦状态改变,就不会再变,任何时候都可以得到这个结果。 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise对象提供统一的接口,使得控制异步操作更加容易。

    1.基本用法

    Promise对象是一个构造函数,用来生成Promise实例。

    var promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } }); // Resolved 将Promise对象的状态从Pending变成Resolved // Rejected 将Promise对象的状态从Pending变成Rejected

    Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

    function timeout(ms) { return new Promise((resolve, reject) => { setTimeout(resolve, ms, 'done'); }); } timeout(100).then((value) => { console.log(value); }); // 第一个回调函数是Promise对象的状态变为Resolved时调用; // 第二个回调函数是Promise对象的状态变为Reject时调用

    Resolve函数的参数还可以是另一个Promise实例;

    var p1 = new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('fail')), 3000) }) var p2 = new Promise(function (resolve, reject) { setTimeout(() => resolve(p1), 1000) }) p2 .then(result => console.log(result)) .catch(error => console.log(error))

    2.promise.prototype.then()

    Promise实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。 then方法返回的是一个新的Promise实例。因此可以采用链式写法,即then方法后面再调用另一个then方法。

    getJSON("/post/1.json").then(function(post) { return getJSON(post.commentURL); }).then(function funcA(comments) { console.log("Resolved: ", comments); }, function funcB(err){ console.log("Rejected: ", err); });

    采用链式的then,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise对象(即有异步操作),这时后一个回调函数,就会等待该Promise对象的状态发生变化,才会被调用。

    3.Promise.prototype.catch()

    Promise.prototype.catch方法是.then(null, rejection)的别名,用于指定发生错误时的回调函数。

    getJSON('/posts.json').then(function(posts) { // ... }).catch(function(error) { // 处理 getJSON 和 前一个回调函数运行时发生的错误 console.log('发生错误!', error); });

    异步操作抛出错误,状态就会变为Rejected。

    // 写法一 var promise = new Promise(function(resolve, reject) { try { throw new Error('test'); } catch(e) { reject(e); } }); promise.catch(function(error) { console.log(error); }); // 写法二 var promise = new Promise(function(resolve, reject) { reject(new Error('test')); }); promise.catch(function(error) { console.log(error); });

    reject方法的作用,等同于抛出错误。

    var promise = new Promise(function(resolve, reject) { resolve('ok'); throw new Error('test'); }); promise .then(function(value) { console.log(value) }) .catch(function(error) { console.log(error) }); // ok

    如果Promise状态已经变成Resolved,再抛出错误是无效的。因为 Promise 的状态一旦改变,就永久保持该状态,不会再变了。

    getJSON('/post/1.json').then(function(post) { return getJSON(post.commentURL); }).then(function(comments) { // some code }).catch(function(error) { // 处理前面三个Promise产生的错误 });

    Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

    4.Promise.all

    Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。 var p = Promise.all([p1, p2, p3]); Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。 p的状态由p1、p2、p3决定,分成两种情况。 (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。 (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

    5.promise.race

    Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

    var p = Promise.race([p1, p2, p3]);

    上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

    const p = Promise.race([ fetch('/resource-that-may-take-a-while'), new Promise(function (resolve, reject) { setTimeout(() => reject(new Error('request timeout')), 5000) }) ]); p.then(response => console.log(response)); p.catch(error => console.log(error));

    上面代码中,如果5秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数。

    6.Promise.resolve()

    有时需要将现有对象转为Promise对象,Promise.resolve方法就起到这个作用。

    Promise.resolve('foo') // 等价于 new Promise(resolve => resolve('foo'))

    Promise.resolve方法的参数分成四种情况。 (1) 参数是一个Promise实例 Promise.resolve将不做任何修改、原封不动地返回这个实例。 (2)参数是一个thenable对象

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

    Promise.resolve方法会将这个对象转为Promise对象,然后就立即执行thenable对象的then方法。 (3)参数不是具有then方法的对象,或根本就不是对象 Promise.resolve方法返回一个新的Promise对象,状态为Resolved。 (4)不带有任何参数 直接返回一个Resolved状态的Promise对象。

    setTimeout(function () { console.log('three'); }, 0); Promise.resolve().then(function () { console.log('two'); }); console.log('one'); // one // two // three

    需要注意的是,立即resolve的Promise对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。

    7.Promise.reject()

    Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected 注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。这一点与Promise.resolve方法不一致。

    const thenable = { then(resolve, reject) { reject('出错了'); } }; Promise.reject(thenable) .catch(e => { console.log(e === thenable) }) // true

    8.done()

    总是处于回调链的尾端,保证抛出任何可能出现的错误。

    asyncFunc() .then(f1) .catch(r1) .then(f2) .done();

    9.finally()

    用于指定不管Promise对象最后状态如何,都会执行的操作。它与done方法的最大区别,它接受一个普通的回调函数作为参数,该函数不管怎样都必须执行。

    server.listen(0) .then(function () { // run test }) .finally(server.stop);

    10.Promise.try

    const f = () => console.log('now'); Promise.resolve().then(f); console.log('next'); const f = () => console.log('now'); Promise.try(f); console.log('next');

    Promise.try为所有操作提供了统一的处理机制,所以如果想用then方法管理流程,最好都用Promise.try包装一下。

    Generator函数

    Generator是个什么东西? Generator 函数是 ES6 提供的一种异步编程解决方案。

    Generator 函数是一个状态机,封装了多个内部状态。执行 Generator 函数会返回一个遍历器对象。

    Generator函数的两个特征:

    function关键字与函数名之间有一个星号;函数体内部使用yield语句,定义不同的内部状态 function* helloWorldGenerator() { yield 'hello'; yield 'world'; return 'ending'; } var hw = helloWorldGenerator();

    如上代码,helloWorldGenerator函数调用后返回一个遍历器,下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。

    hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'ending', done: true } hw.next() // { value: undefined, done: true }

    next方法参数

    yield句本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。

    function* foo(x) { var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true} var b = foo(5); b.next() // { value:6, done:false } b.next(12) // { value:8, done:false } b.next(13) // { value:42, done:true }

    3.应用

    (1)异步操作的同步化表达

    Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行。

    function* main() { var result = yield request("http://some.url"); var resp = JSON.parse(result); console.log(resp.value); } function request(url) { makeAjaxCall(url, function(response){ it.next(response); }); } var it = main(); it.next();

    (2)控制流管理 如果有一个多步操作非常耗时,采用回调函数,可能会写成下面这样。

    step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); });

    采用Promise改写上面的代码。

    Promise.resolve(step1) .then(step2) .then(step3) .then(step4) .then(function (value4) { // Do something with value4 }, function (error) { // Handle any error from step1 through step4 }) .done();

    上面代码已经把回调函数,改成了直线执行的形式,但是加入了大量Promise的语法。Generator函数可以进一步改善代码运行流程。

    function* longRunningTask(value1) { try { var value2 = yield step1(value1); var value3 = yield step2(value2); var value4 = yield step3(value3); var value5 = yield step4(value4); // Do something with value4 } catch (e) { // Handle any error from step1 through step4 } } scheduler(longRunningTask(initialValue)); function scheduler(task) { var taskObj = task.next(task.value); // 如果Generator函数未结束,就继续调用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } }

    4.Tunk函数

    Thunk 函数是自动执行 Generator 函数的一种方法。

    var Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); } }; }; function f(a, cb) { cb(a); } let ft = Thunk(f); let log = console.log.bind(console); ft(1)(log) // 1

    async

    async 函数是什么?一句话,它就是 Generator 函数的语法糖。 如下一个 Generator 函数,依次读取两个文件

    var fs = require('fs'); var readFile = function (fileName) { return new Promise(function (resolve, reject) { fs.readFile(fileName, function(error, data) { if (error) reject(error); resolve(data); }); }); }; var gen = function* () { var f1 = yield readFile('/etc/fstab'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };

    写成async函数,就是下面这样。

    var asyncReadFile = async function () { var f1 = await readFile('/etc/fstab'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); };
    转载请注明原文地址: https://ju.6miu.com/read-39115.html

    最新回复(0)