javascript与其他语言的经典不同在于,javascript是异步的,而其他语言是同步的。这里,我们介绍一下javascript中异步的几种方式。

几种异步方式

  • 回调函数
  • promise
  • generator
  • async / await

回调函数

回调函数,是早期js中广泛使用的一种回调方式,jquery中的ajax方法就是经典的回调函数模式。

回调函数的写法中,回调是放在函数参数里面的,执行的过程看起来没有同步写法那么明了。

在回调次数多的情况下,可能会造成回调地狱,就是嵌套太深。所以es后面提出的异步方案也是不断对异步做优化。

$.ajax({
    url: 'http://binnie.com/getdata',
    type: 'GET',
    data: {},
    success: {
        // success todo
    },
    error: {
        // error todo
    }
})
复制代码

promise

promise,是es6中提供的一种解决方案,相比于回调函数,promise的写法更加同步化一些~

状态

promise的状态分为三种:

  • pending
  • resolved
  • rejected

状态的变化只有两个过程,并且状态一旦发生改变,就永远不可逆。

  • pending -> resolved
  • pending -> rejected

基础

Promise是一个构造函数,我们可以使用new来进行实例化的操作。

我们来看一个简单的?

我们可以看到new一个Promise对象的时候,传入的是一个函数,该函数携带两个参数:resolve&reject,分别为成功操作&失败操作,函数内部的操作是同步执行的,所以当执行代码的时候,马上就可以打印出1

new返回的对象是一个Promise对象,Promise对象有几个内置的函数,这几个函数是在Promise的同步函数执行完之后可能触发的函数。

每次执行完其中一个函数,是可以接着链式执行的,因为每次执行完一次,都会返回一个新的Promise对象。

  • then
  • finally
  • catch
let promise = new Promise((resolve, reject) => {
    // 这里是同步执行的
    console.log(1)
    // 异步操作
    return resolve('binnie')
});
promise.then((resolve) => {
    // 成功回调,即执行resolve()
    console.log('then')
},(reject) => {
    // 失败回调,即执行reject()
    console.log('err')
}).finally(() => {
    // 完成回调,无论成功/失败都会到的回调
    console.log('finally')
}).catch((err) => {
    // 错误回调,前面任何一步抛出的错误
    console.log(err)
});

// 1
// then
复制代码

实例方法

实例方法是new Promise返回的对象的原型方法。比如then方法 -> Promise.prototype.then。

then

then方法接受两个参数,第一个为resolve回调,第二个为reject回调

promise.then((resolve) => {
    // resolve参数为promise resolve的时候的参数
    // 当promise执行resolve时,会进入该回调
},(reject) => {
    // reject参数为promise reject的时候的参数
    // 当promise抛出异常 或者 执行reject时,会进入该回调
})
复制代码

finally

finally回调函数不接受任何参数,只做finally处理,类似请求的complete操作。

promise.finally(() => {
    // resolve or reject 之后的操作
})
复制代码

catch

catch方法,用于捕获异常。

在promise中的异常可以在两个位置进行捕获,then方法的第二个函数 or catch方法。一般情况下,我们会在catch中捕获。

在promise中,错误也是会冒泡的,如果then操作没有添加reject方法,那么错误就会在后面的catch捕获,不过,如果没有对获取进行捕获,错误其实会被promise自己吃掉,而不会暴露在最外面。

promise.then(() => {
    console.log('then')
    throw new Error()
}).catch((err) => {
    console.log(err)
})
复制代码

Promise方法

Promise方法,直接调用即可,返回的也是promise对象。

Promise.all(array)

Promise.all,接受一个数组做为参数,参数中的对象会被转化为promise对象

当数组中的所有promise对象resolve时,all的promise的状态才变为resolve

当数组中的任一promise对象reject时,all的promise的状态就变为reject

Promise.all 类似我们平常使用的 &&

Promise.all([promise,2,3]).then((resolve) => {
    // 参数也是一个数组,为all数组对应的resolve参数
    // ['binnie',2,3]
    console.log(resolve)
}).catch((err) => {
    // 错误为第一个reject时抛出的错误
    console.log(err)
})
复制代码

Promise.race(array)

Promise.race跟all的用法类似,也是接受一个数组做为参数

数组中最先改变状态的,那个状态就会同步给race的状态

Promise.race([1,promise,3]).then((resolve) => {
    // 参数为第一个状态变为resolve的参数
    console.log(resolve)
}).catch((err) => {
    // 参数为第一个状态变为reject的参数
    console.log(err)
})
复制代码

Promise.resolve()

返回一个状态为resolved的promise对象。

Promise.resolve()可携带参数,参数会做为返回对象的参数。

Promise.reject()

返回一个状态为rejected的promise对象。

Promise.reject()可携带参数,参数会做为返回对象的参数。


generator

generator可以理解为一个暂停执行函数。每次执行next()来往下走,直到结束,状态停止。

generator函数的写法,就是在函数后面加一个星号 function* ,在函数内部,可以使用 yield 来进行暂停的操作。

function* foo() {
    console.log(0)
    yield 1
    yield 2
    yield 3
    return 4
    yield 5
}
// 调用generator函数的时候,函数并不会被执行
let f = foo()
console.log(f)         
// 调用next时函数才会开始执行,当遇到yield时暂停
// 下一次next时会从上次暂停的位置继续执行
// 当遇到return时 or 函数执行完成时,函数结束
console.log(f.next())   
console.log(f.next())
console.log(f.next())
console.log(f.next())
console.log(f.next())
复制代码



async / await

generator相对来说可读性会比较差,于是乎有来async函数,表示函数内部会有异步操作,async函数相当于是generator函数的语法糖。

下面我们来看下async函数的写法

// 定义一个async函数,表明函数内部要执行异步操作
async function fun() {
    // await后面跟着异步操作
    // 函数会等待await操作完成之后再继续往下走
    let obj = await request();
    return obj;
}
// async函数执行完之后会返回一个promise对象,函数返回值可再then中取到
let ret = fun()
ret.then((resolve) => {
    console.log(resolve)
})
复制代码

当async函数中遇到await时,就会进行等待,然后再执行下一步的操作,如果没必要串行,我们可以改成并行的写法,这样可以节省函数执行时间。

// 串行写法
async function fun() {
    let obj1 = await request1();
    let obj2 = await request2();
    return;
}

// 并行写法
async function fun() {
    let obj1 = request1();
    let obj2 = request2();
    let ret1 = await obj1;
    let ret2 = await obj2;
    return;
}

// Promise.all写法
async function fun() {
    let [obj1, obj2] = await Promise.all([request1(), request2()]) ;
    return;
}
复制代码

写在最后

异步操作的核心都是一样的,只是写法不同而已。

具体要采用哪种异步方法,还是要看我们实际的项目需要。