promise、async、await有什么用呢,怎么理解?

都是为了解决异步回调产生的。

Promise好比容器,里面存放着一些未来才会执行完毕的事件的结果,而这些结果一旦生成是无法改变的。

async和await遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。

Promise解决了什么问题呢?底层代码是怎样的?

在之前没有promise的时候,我们处理多个异步请求回调是一层一层嵌套的,第一个函数的输出是第二个函数的输入,比如:

ajax.get(url, function(id) {
    ajax.get({id}, function() {
        ajax.get({name}, function() {
            ......
        })
    })
})

如果业务逻辑复杂,且基本上我们对于请求回来的数据还得做一系列的处理,这样的代码对于后期的可阅读性和可维护性都十分不友好,那么promise的链式调用就解决了多层异步嵌套回调的问题。且代码可读性和可维护性都会提高。

接下来我们来实现一下Promise的简易版

先来简述一下思路:

  • promise有三种状态pending、fulfilled、reject
  • new Promise调用的时候需要传入一个executor执行器函数,该函数会立即执行;
  • executor函数接收两个参数,resolve,reject,分别对应异步请求成功执行和失败执行;
  • 设置默认状态stratus为pending,请求成功状态下的值为value,默认值为undefined,请求失败下的值为reason,默认值为unfined
  • promise的状态值只能从pending -> fulfilled 或者 pending -> reject,状态一旦确定就不会在改变
  • promise有一个then方法,接收两个参数onFulfilled、onRejected,分别为异步请求成功的回调和失败的回调。

好了,接下来来实现一个简易版的Promise    

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';

class MyPromise {
    constructor(executor) {
        this.status = PENDING; // 设置默认状态值
        this.value = undefined; // 异步请求成功的值,默认为undefined
        this.reason = undefined; // 异步请求失败的值,默认为undefined  
        // 异步请求成功调用
        let resolve = value => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
            }
        }
        // 异步请求失败调用
        let reject = reason => {
            if (this.status === PENDING) {
                this.status = REJECT;
                this.reason = reason;
            }
        }
        try {
            // 立即执行
            executor(resolve, reject);
        } catch(error) {
            // 抛异常执行失败逻辑
            reject(error);
        }
    }
    // then方法,根据状态值判断是执行成功回调还是失败回调
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
             onFulfilled(this.value);
        }
        if (this.status === REJECT) {
             onFulfilled(this.reason);
        }    
    }
} 

可以测试一次,正常输出,哇哦,实现了,但是不要着急,它只是实现了同步操作的Promise,那么我们来继续优化我们的代码

new MyPromise((resolve, reject) => {
    resolve('hello world!');
}).then((res) => {
    console.log(res); // hello world!
})

如果在executor中传入一个异步操作呢,会有什么样的结果呢?

new MyPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('hello world');
    }, 1000)
}).then((data) => {console.log(data)})

我们发现什么输出也没有,这个是为什么呢?

因为MyPromise调用then方法,当前的promise并没有成功,一直处于pending状态,所以我们需要当调用then方法时,如果当前状态是pending,需要将成功和失败的回调分别存放起来,在executor的异步任务被执行时,触发resolve或reject,再依次调用成功和失败的回调。根据这个思路来优化一下代码

// 三个状态
const PENDING = 'PENDING'
const FULFILLED = 'FULFILLED'
const REJECT = 'REJECT'
// 基础版的Promise
class MyPromise {
    constructor(executor) {
        this.status = PENDING; // 设置默认状态
        this.value = undefined; // 存放成功状态的值,默认为undefined
        this.reason = undefined; // 存放失败状态的值,默认为undefined
        this.onResolveCallbacks = []; // 存放成功的回调
        this.onRejectedCallbacks = []; // 存放失败的回调
        // 成功回调
        let resolve = value => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolveCallbacks.forEach(fn => fn()); // 依次执行成功回调
            }
        }
        // 失败回调
        let reject = reason => {
            if (this.status === PENDING) {
                this.status = REJECT;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn()); // 依次执行失败回调
            }
        }
        try {
            // 立即执行,将resolve和reject函数传给使用者
            executor(resolve, reject);
        } catch(e) {
            // 发生异常时执行失败逻辑
            reject(e);
        }
    }
    // 包含一个then方法,并接收两个参数
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value);
        }
        if (this.status === REJECT) {
            onRejected(this.reason);
        }
        // 如果状态为pending,需要依次将onFulfilled和onRejected函数保存起来,等待状态确定后再依次的将对应的函数执行
        if (this.status === PENDING) {
            this.onResolveCallbacks.push(() => {
                onFulfilled(this.value);
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason);
            })
        }
    }
}

再次给executor传一个异步函数执行一下,发现过了一秒后输出了,哈哈哈,解决了异步操作的Promise 

通过以上代码的编写,发现了这个其实是一个发布订阅模式:收集依赖 -> 触发通知 -> 取出依赖执行,这种方式被广泛用于发布订阅模式的实现。

promise的优势在于可以链式调用,在我们使用Promise的时候,当then函数中return了一个值,不管是什么值,我们都能在下一个then中获取到,这个就是所谓的then的链式调用,而且当我们不在then中放入参数,比如:promise.then().then(),那么其后面的then依旧可以得到之前then返回的值,这个就是所谓的值的穿透。那么具体应该怎么实现呢?

如果每次调用then的时候都重新创建一个promise对象,并把上一个then的返回结果传给这个新的promise的then方法,不就可以一直then下去了嘛,那么我们来捋一下思路:

  • then的参数有onFulfilled onRejected,可以缺省,如果这两个参数不为函数将其忽略,且依旧可以在下面的then中获取到之前返回的值
  • promise可以then多次,每次执行完promise.then返回的都是一个新的promise
  • 如果then的返回值 x 是一个普通值,那么就将这个结果作为参数传递给下一个then的成功的回调
  • 如果then抛出了异常,就把这个异常作为参数传给下一个then的失败的回调
  • 如果then的返回值 x 是一个promise对象,那么等待这个promise执行完毕,执行成功则走下一个then的成功回调,失败就走下一个then的失败回调;抛异常走下一个then的失败
  • 如果then的返回值 x 和promise是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个then的失败回调
  • 如果then的返回值 x 是一个promise,且该返回值同时调用resolve和reject,则第一次调用优先,其他调用忽略。

完整代码如下:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECT = 'reject';

const resolvePromise = (promise2, x, resolve, reject) => {
    // 自己等待自己完成,是一个错误的实现,用一个类型错误结束掉promise
    if (promise2 === x) {
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
    }
    let called;
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    reject(r);
                })
            } else {
                resolve(x);
            }
        } catch (error) {
            if (called) return;
            called = true;
            reject(r);
        }
    }
}

class MyPromise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolveCallbacks = [];
        this.onRejectedCallbacks = [];

        let resolve = value => {
            if (this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolveCallbacks.forEach(fn => fn());
            }
        }

        let reject = reason => {
            if (this.status === PENDING) {
                this.status = REJECT;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }

        try {
            executor(resolve, reject);
        } catch (error) {
            reject(error);
        }
    }

    then(onFulfilled, onRejected) {
        // 解决 onFufilled,onRejected没有传值的问题
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
        // 因为错误的值要让后面访问到,所以这里也要抛出个错误,不然会在之后then的resolve中捕获
        onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err}
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === PENDING) {
                this.onResolveCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                });

                this.onRejectedCallbacks.push(()=> {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0);
                });
            }
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject;
                    }
                }, 0)
            }
            if (this.status === REJECT) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (error) {
                        reject(error);
                    }
                }, 0)
            }
        })
        return promise2;
    }
}

promise与async、await的区别?

  • promise的出现解决了传统callback函数导致的地狱回调问题,但是他的语法导致它纵向发展形成了一个回调链,遇到复杂的业务场景显然是不美观的;
  • async、await看起来更加简洁,使得异步代码看起来像同步代码,只有await的代码执行完毕后才会执行下面的代码,与promise一样,也是非阻塞的;
  • async/await基于Promise实现,相当于Promise的升级版,不能用于普通的回调函数;

总体来说它们是解决的同一个问题,达到的效果也是大同小异的,可以根据自己的需求自行选择使用哪个。

async和await的使用

要理解async函数,首先得来了解一下 Generator 函数。因为 async和await遵循的是 Generator 函数的语法糖

Generator函数生成器的理解:

是es6引入的一个数据类型,相当于一个状态机,内部封装了很多状态,同时返回一个迭代器对象。可以通过这个迭代器遍历相关的值和状态。

Generator的显著特点是可以多次返回,每次的返回值作为迭代器的一部分保存下来,可以被我们显式调用。

Generator函数的调用:

  • 普通的函数通过 function 声明,return作为回调,可以回调一次;
  • Generator通过 function* 定义,除了 return 还使用 yield 返回多次。

Generator的方法:next、return、throw

  • next 是一步一步的来执行,每次返回一个值,每次也可以传入新的值参与计算;
  • return 则直接跳过所有步骤,返回{value: undefined, done: true};
  • throw 则根据函数中书写try/catch方法,返回catch中的内容,如果没有try,则直接抛错。

Generator返回值:

  1. 是一个带有状态的Generator实例。可以仅能通过for of调用遍历,返回他的所有状态。
  2. 调用 for of 方法后,在后台调用next(), 当 done 属性为 true 的时候,循环退出。因此Generator函数的实例将顺序执行一遍,再次调用时,状态为已完成。

状态的存储和改变:

yield返回的值是可以被变量存储和改变的。next中传入的参数是会替换掉上一步生成值的。

下面通过一个简单例子看一下Generator函数的使用:

function foo*(n) {
    let a = yield n + 1;
    let b = yield a + 2;
    yield n;
    yield a;
    yield b;
}

let result9 = foo(0);
result.next(); // {value: 1, done: false}
result.next(5); // {value: 7, done: false} 传入参数5,会替换掉上一步计算的结果,即 a = 5
result.next(6); // {value: 0, done: false} 传入参数6,会替换掉上一步计算的结果,即 b = 6
result.next(7); // {value: 5, done: false}
result.next(8); // {value: 6, done: false}

yield*委托:在Generator函数中,我们有时需要将多个迭代器的值合在一起,我们可以使用yield *的形式,将执行委托给另外一个Generator函数

function* foo1() {
    yield 1;
    yield 2;
    return "foo1 end";
}
function* foo() {
    yield* foo1();
      yield 5;
}
const result = foo();
result.next(); // {value: 1, done: false}1
result.next(); // {value: 2, done: false}
result.next(); // {value: 5, done: false}
result.next(); // {value: undefined, done: true}

发现foo1 end未输出。并且return的作用不就是执行到就会直接跳出整个流程,为什么还会向后执行呢?

原因是在委托的时候,所有的yield*都是以函数表达式的形式出现。return的值是表达式的结果,在委托结束之前其内部都是暂停的,等待到表达式的结果的时候,将结果直接返回给foo。此时foo内部没有接收的变量,所以未打印。

以上总体例子如下:

function* foo(n) {
    yield n+1;
    try {
        yield n+2;
        return n+3;
        yield n+6;
    } catch(e) {
        console.log('catch it');
    }
}
const result  = foo(1);
// 情况一
result.next(); // {value: 2, done: false}
result.throw(); // 抛错,因为还没进到try方法里面
result.next(); // {value: undifined, done: true}

// 情况二
result.next(); // {value: 2, done: true}
result.next(); // {value: 3, done: true}
result.throw(); // catch it {value: undefined, done: true}
result.next(); // {value: undifined, done: true}

// 遍历
let arr = [];
for (let i of result) {
    arr.push(i);
}
console.log(arr); // [2, 3] 因为return了,后面的就不会执行了。

简单的实现一个async/await

// 定义Promise,用来模拟异步请求
function getNum(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(num + 1);
        }, 1000)
    })
}

// 自动执行器,如果一个Generator函数没有执行完则递归调用。
function asyncFun(func){
  var gen = func();

  function next(data){
    var result = gen.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
}

// 所需要执行的Generator函数,内部的数据在执行完成一步的promise之后,再调用下一步
var func = function* (){
  var f1 = yield getNum(1);
  var f2 = yield getNum(f1);
  console.log(f2) ;
};
asyncFun(func);

promise的简单使用

// 获取用户信息
const getUserInfo = new Promise((resolve, reject) => {
    axios.get(url).then(({data}) => {
        resolve(data);
    }).catch(({err}) => {
        reject(err);
    })
})
// getUserInfo返回一个promise,可以用它的then方法注册成功回调。这种方式使的异步调用变的十分顺手。
getUserInfo.then(data => {
    // 一些处理
    console.log(data);
})

 

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐