异步编程

JavaScript异步编程

JavaScript异步编程

Promise

Promise是异步编程的一种实现方式,它比传统的解决方案更加合适、强大。所谓Promise,就是一种容器,容器里面保存着未来才会发生的事情(一般是异步操作)的结果。

Promise是一个对象,它提供一系列的API可以将异步操作的事情以同步的形式表现出来,比起”回调地狱”,它看起来更加合适、强大。它具有以下几大特点:

  • Promise的状态不受任何外界的影响。Promise的异步操作有三种状态: pending(执行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果才能够决定Promise状态的走向。
  • Promise的状态一旦改变,变不可再次变动。它的状态变化只有两种情况:pending => fulfilledpengding => rejected。状态一旦发生变化,就不再改变,称之为 resolved(已定型)。

Promise一旦新建之后,就会立即执行。而then指定的回调函数只有同步操作执行完之后才会执行。

const promise = new Promise((resolve,reject) => {

});
promise.then(() => {//成功回调
  //todo
},(error) => {//失败回调

}).catch(e => {
  //todo
})

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。但是一般默认不指定rejected状态的回调函数,而是使用catch

finally不管promise最终的执行结果如何,都会执行。

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

Promise.all()方法接受一个数组作为参数,p1p2p3都是 Promise 实例,如果不是,就会先调用Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例

举个🌰:

var p1 = new Promise(resolve => {
  setTimeout(() => {
    resolve({
        msg: 'p1'
    })
  }, 3000)
});
var p2 = new Promise(resolve => {
  setTimeout(() => {
    resolve({
        msg: 'p2'
    })
  }, 1000)
});
var p3 = new Promise(resolve => {
  setTimeout(() => {
    resolve({
        msg: 'p3'
    })
  }, 500)
});
let p = Promise.all([p1,p2,p3]);
p.then(([res1,res2,res3]) => {
  console.log(res1);
  console.log(res2);
  console.log(res3);
}).catch(e => {

});
//执行结果:p1 p2 p3

p的状态由p1,p2,p3决定:

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

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

let p = Promise.race([p1, p2, p3]);
p.then((res) => {
    console.log(res);
});

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

allSettled:该方法由ES2020引入。Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。

anyPromise.any()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。该方法目前是一个第三阶段的提案

resolvePromise.resolve()将现有对象转为 Promise 对象。

  • 参数是一个Promise实例:那么Promise.resolve将不做任何修改、原封不动地返回这个实例。

  • 参数是一个 thenable 实例:thenable对象指的是具有then方法的对象,比如下面这个对象。:

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

Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。

  • 参数不是具有then方法的对象,或根本就不是对象:如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved
const p = Promise.resolve('Hello');

p.then(function (s){
  console.log(s)
});
// Hello

rejectPromise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected

Generator/yield

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。

Generator 函数有多种理解角度。语法上,Generator 函数是一个状态机,封装了多个内部状态。

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征:

1、function关键字与函数名之间有一个星号;

2、函数体内部使用yield表达式,定义不同的内部状态

Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
let hwg = helloWorldGenerator();
hwg.next();//{ value:hello,done:false }
hwg.next();//{ value:world,done:false }
hwg.next();//{ value:ending,done:true }
hwg.next();//{ value:undefined,done:true }

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

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

async/await

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。它是Generator的语法糖。async函数就是将 Generator 函数的星号(*)替换成async,将yield替换成await

async函数对 Generator 函数的改进,体现在以下几点。

  • 内置执行器。

Generator 函数的执行必须靠执行器。而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。

  • 返回值是 Promise

async函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用then方法指定下一步的操作。

进一步说,async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖。

async函数返回一个 Promise 对象。async函数内部return语句返回的值,会成为then方法回调函数的参数。

async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。也就是说,只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

举个🌰:

function timeFn(s){
  //Promise 新建后就会立即执行。
  return new Promise((resolve,reject) => {
    setTimeout(() => {
      resolve({
        msg:'hello'
      })
    },s)
  })
}
async function fun(time){
  //Promise的写法
  // timeFn(time).then(res => {
  //     console.log(res);
  // })

  //async await的写法
  let str = await timeFn(time);
  console.log(str);
}
fun(3000)

程序会在3s之后打印{msg:’hello’}

await:正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。另一种情况是,await命令后面是一个thenable对象(即定义then方法的对象),那么await会将其等同于 Promise 对象。

如果await后面的异步操作出错,那么等同于async函数返回的 Promise 对象被reject

await命令只能用在async函数之中,如果用在普通函数,就会报错。

async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。

async function fn(args) {
  // ...
}

// 等同于

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
//spawn 自动执行器
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!