javascript异步编程解决方案

javascript异步编程解决方案

用JS写程序,避免不了要进行异步编程,这篇文章将介绍JS中的几种异步编程的解决方案,若有错误请指正。

1、Promise

大名鼎鼎的promise,作为异步编程的首选被众多前端工程师采用,但是关于promise的细节这里做一些补充。
他是最早由社区提出的一种解决异步方案,后来被ES6收入规范,原生提供了Promise对象。promise和以往的回调函数相比有更优雅和合理的处理方式以及写法。
事实上promise是一个对象。单词也是承诺的意思,在JS中表示对当前异步状态的留存。 这个对象的特点有以下两点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected
(3) Promise 新建后就会立即执行。

用法:
下面代码创造了一个Promise实例。

const promise = new Promise(function(resolve, reject) {
    if (/* 异步操作成功 */){
    resolve(value);
    console.log('hello')
    } else {
    reject(error);
    }
    });

这个例子中有两个函数,resolve和reject,这两个函数是由promise内置的,不用自己声明。理解这两个函数对于改变promise的状态至关重要。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;在调用resolve后后面的代码仍然会执行,上列中的‘hello’会输出在控制台中,除非我们用return返回resolve。
reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

then方法:
then方法是定义在promise原型对象上的。then方法可以接受promise返回的结果,接受两个回调函数作为参数,分别代表成功和失败。 then方法返回的是一个新的Promise实例, 因此可以采用链式写法。

  promise.then(function(value) {
  // success
}, function(error) {
  // failure
});

链式写法:

promise.then(function(value) {
  // success
}, function(error) {
  // failure
}).then(res =>{
 
})

catch方法
catch用于指定发生错误时的回调函数。

const promise = new Promise(function(resolve, reject) {
  throw new Error('error');
});
promise.catch(function(error) {
  console.log(error);
});

当promise抛出错误时会被catch捕获,then()方法指定的回调函数,如果运行中抛出错误,也会被catch()方法捕获。
catch方法也可以链式调用:

promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

如果promise抛出错误,而没有catch方法,此错误不会被抛出到promise外,也不会影响外部代码的执行。

finally方法

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {console.log('world')});

'world’始终会输出在控制台。

all方法

Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p1,p2,p3三个promise对象的状态决定p的状态。影响策略如下

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

race方法
在形式上race与all相同:

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

不同点在于:

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

即所谓的竞争状态。

allSettled方法
从字面上解读,allSettled可以理解为所有的promise都执行完或者状态确定了就执行allSettled方法。

const promises = [
  fetch('/api-1'),
  fetch('/api-2'),
  fetch('/api-3'),
];

await Promise.allSettled(promises);
goOn();

如上所示,
数组promises包含了三个请求,只有等到这三个请求都结束了(不管请求成功还是失败),goOn()才会执行。

any方法

Promise.any([
  fetch(),
  fetch(),
  fetch()
]).then((first) => {  // 只要有一个 fetch() 请求成功
  console.log('first');
}).catch((error) => { // 所有三个 fetch() 全部请求失败
  console.log('error');
});

从上述的实例来看,any方法接受多个promise对象,一旦有一个成功则any的状态就变为fulfilled,如果三个promise都为失败状态,则any状态为rejected状态。

当然关于promise还有其他一些方法,比如try()方法,这里不多介绍,大家可以看阮一峰老师关于promise的介绍(https://es6.ruanyifeng.com/#docs/promise)

下面第二种异步编程方法

2、async

async在2017年被写入es标准中,作为异步解决方案,他更优雅。

async函数返回一个 Promise对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

如:

async function A() {
  const D = await B();
  const E = await C();
  return E;
}

当函数被async声明时,表明其内部含有异步操作,await 后面的B()方法作为一个异步求值方法,当B请求结束时程序会进行下以步,开始执行函数C()。这几乎使用同步代码的形式执行了两个异步操作。细心地朋友会发现这类似于生成器函数的指针操作。

async 函数有多种使用形式。

  // 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// Class 的方法
class Storage {
  constructor() {
    this.cachePromise = caches.open('avatars');
  }
  async getAvatar(name) {
    const cache = await this.cachePromise;
    return cache.match(`/avatars/${name}.jpg`);
  }
}
const storage = new Storage();
storage.getAvatar('jake').then();
// 箭头函数
const foo = async () => {};

await函数

正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。

async function f() {
  // 等同于
  // return 123;
  return await 123;
}

f().then(v => console.log(v))
// 123

如果await 返回了一个reject状态,reject的参数会被catch方法的回调函数接收到。

async function f() {
  await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

重点
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

async function f() {
  await Promise.reject('出错了');
  await Promise.resolve('hello world'); // 不会执行
}

如果要跳过错误继续执行,则需要用try…catch捕获此次错误。

async function f() {
  try {
    await Promise.reject('出错了');
  } catch(e) {
  }
  return await Promise.resolve('hello world');
}

f()
.then(v => console.log(v))
// 出错了
// hello world

3、Generator

Generator字面竟然是发电机?,为什么要用这个词,着实也让我一脸懵。但也是 ES6提供的一种异步编程解决方案,从名字就可以看出来这个玩意儿和传统的函数写法不同。

function* F() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

let w = F();

形式上,Generator函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态

但是!调用F()后生成器函数并不执行。我们需要调用函数内部的next方法才能执行

w.next()
// { value: 'hello', done: false }

w.next()
// { value: 'world', done: false }

w.next()
// { value: 'ending', done: true }

调用 Generator 函数,返回一个遍历器对象,代表 Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

如果next方法已经执行到末尾,那么再次调用后结果为:

w.next()
// { value: undefined, done: true }

Generator内部也可以不用yield表达式,那么此时:

function* F() {
  return 'ending';
}

let w = F();

仍然要用 w.next()才能获取返回值
重点:
next方法可以传值,如下:

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

传值只对当前的yield表达式有效。
那么如何实现异步操作?

Generator 函数可以暂停执行和恢复执行,这是它能封装异步任务的根本原因。

function run(fn) {
  var gen = fn();
  function next(err, data) {
    var result = gen.next(data);
    if (result.done) return;
    result.value(next);
  }
  next();
}
var g = function* (){
  var f1 = yield fetch('fileA');
  var f2 = yield fetch('fileB');
  // ...
  var fn = yield fetch('fileN');
};
run(g);

上述代码用类似递归的方式实现了多个异步请求的执行

三种异步方法的比较:
首先:Promise方法虽然解决了异步操作,但是有很多自身的API,语义化不够明显,本身的then方法链式调用在面对多个请求时,多个链式操作不够友好
其次: Generator 函数,优点是语义比 Promise 写法更清晰,但是奇怪的用法可能需要适应,不易理解。
最后:Async 函数,Async 函数的实现最简洁,最符合语义,几乎没有语义不相关的代码.

那么如果你在解决异步操作时,会选择哪种呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

铁锅炖大鹅(e)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值