promise与异步编程
我们都知道,没有promise的时代,异步的实现是通过回调来实现的。造成的结果就是回调地狱,书写代码的形式就是一层嵌套一层,看起来非常不直观。
后来ES5出来了promise的解决方案,书写代码以链式的方式来调用。我们看2个简单的例子。
let promise = readFile('example.txt')
promise.then(
function(contents){
console.log(contents)
},
function (err) {
console.log(err)
})
生成器
由于promise的书写方式还是不够直观,所有ES6出来了生成器的概念。那什么是生成器呢?
生成器是一种返回迭代器的函数,通过function 关键字后的星号(*)来表示,函数中会用到新的关键字yield,星号可以紧挨着funcition关键字,也可以在中间添加一个空格。
function *createIterator() {
yield 1;
yield 2;
yield 3;
}
// 生成器的调用方式与普通函数相同,只不过返回的是一个迭代器
let itor = createIterator()
console.log(itor.next().value) 1
console.log(itor.next().value) 2
console.log(itor.next().value) 3
生成器的定义中包含了迭代器,那我们有必要学习一下什么是迭代器:
迭代器是一种特殊的对象,它具有一些专门为迭代过程设计的专有接口,所有的迭代器对象都有一个next()方法,每次调用都返回一个结果对象,结果对象有2个属性,{value: "", done: true|false}.迭代器还会保存一个内部指针,用来指向当前集合中的位置,每次调用next().都会返回写一个可用的值。
我们使用ES5 语法创建一个迭代器:
function createIterator(items){
var i = 0;
return {
next: function () {
var done = (i >= items.length)
var value = !done ? items[i++] : undefined
return {
done: done,
value: value
}
}
}
}
var iterator = createIterator([1,2,3])
iterator.next() {value: 1, done: false}
iterator.next() {value: 2, done: false}
iterator.next() {value: 3, done: false}
iterator.next() {value: undefine, done: true}
iterator.next() {value: undefine, done: true}
// 之后所有的调用都返回相同的内容。
通过以上的分析,我们解释了什么是生成器 和 迭代器, 而且明确了他们之间的关系,下面我是看看使用生成器如何实现异步操作:
生成器实现异步方案
function* fun() {
//myAjax返回promise,执行异步操作
let a = yield myAjax('...')
let b = yield myAjax('...')
yield myAjax('...')
}
// 生成迭代器f, 执行到第一个yield停止,yield后的语句不会执行
var f = fun()
// 执行到第一个yield的语句
var g = f.next()
// 第一个data是第一个yield后面的代码执行后返回的异步结果。
g.value.then(function (data1) {
// 通过这一句代码,可以把data1赋值与a
var g = f.next(data1)
g.value.then(function(data2){
// 通过这一句代码,可以data2赋值与b
f.next(data2)
})
},
function () {})
思考: 如果可以让以上过程自动执行,不就可以实现异步操作了吗,并且书写代码的方式也不用存在嵌套了。
封装生成器自动执行next
function* fun() {
//myAjax返回promise,执行异步操作
let a = yield myAjax('...')
let b = yield myAjax('...')
yield myAjax('...')
}
var f = fun()
function handle(res){
if (res.done) return
res.value.then(function (data) {
// 递归调用
handle(f.next(data))
})
}
handle(f.next())
基于以上的原理,JS社区出现了一个大牛 TJ Holowaychuk,写了著名的co函数。
function* fun() {
//myAjax返回promise,执行异步操作
let a = yield myAjax('...')
let b = yield myAjax('...')
yield myAjax('...')
}
function co(fun) {
// fun 是生成器函数
var f = fun()
function handle(res) {
if (res.done) return
res.value.then(function (data) {
handle(f.next(data))
})
}
handle(f.next())
}
co(fun)
好了,通过以上的分析,我们看看以下一段代码, 2者的结构是不是很相似呢,其实ES7的async/await就是一个语法糖,其底层帮我们实现了co的操作,不用像ES6 一样需要借助co函数来实现异步了。
function* fun() {
let a = yield myAjax('...')
let b = yield myAjax('...')
}
async function fun2() {
let a = await myAjax('...')
let b = await myAjax('...')
}
实现一个简单版的promise
我们先熟悉以下promise的一些基本的概念
• Promise 就是一个类,在执行这个类的时候 需要传第一个执行器进去,执行器会立即执行
• Promise 中有三种状态 分别为成功 fulfilled, 失败 rejected, 等待 pending, pending -> fulfilled pending -> rejected 一旦状态确定就不可更改
• resolve 和 reject 函数是用来更改状态的 resolve : fulfilled , reject : rejected
• then 方法内部做的事情就是判断状态 如果状态是成功 就调用成功的回调函数,如果状态是失败 调用失败的回调函数 then 方法是被定义在原型对象当中的
• then 成功回调有一个参数, 表示成功之后的值, 失败回调有一个参数,表示失败的原因
let promise = new Promise ((resolve,reject)=>{
resolve('成功')
reject('失败')
})
promise.then(()=>{},()=>{})
实现一个最基本的promise,同步的情况
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
constructor(execurot){
execurot(this.resolve,this.reject);//立即执行的执行器
}
// promise 状态
status = PEMDING;
// 成功之后的值
value = undefined;
// 失败的原因
reason = undefined;
resolve = value => { //箭头函数是为了避免 this 指向出问题, this 要指向class本身
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 将状态改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
}
reject = reason => {
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 将状态改为失败
this.status = REJECTED;
// 保存失败之后的原因
this.reason = reason
}
then(successCallback,failCallback){
// 判断状态
if(this.status === FULFILLED){
successCallback(this.value)
}else if(this.status === REJECTED){
failCallback(this.reason);
}
}
}
考虑异步的情况
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
constructor(execurot){
execurot(this.resolve,this.reject);//立即执行的执行器
}
// promise 状态
status = PEMDING;
// 成功之后的值
value = undefined;
// 失败的原因
reason = undefined;
// 成功回调
successCallback = undefined;
// 失败回调
failCallback = undefined;
resolve = value => { //箭头函数是为了避免 this 指向出问题 要指向class
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 将状态改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// 判断成功回调是否存在
this.successCallback && this.successCallback(this.value);
}
reject = reason => {
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 将状态改为失败
this.status = REJECTED;
// 保存失败之后的原因
this.reason = reason
// 判断失败回调是否存在
this.failCallback && this.failCallback(this.reason);
}
then(successCallback,failCallback){
// 判断状态
if(this.status === FULFILLED){
successCallback(this.value)
}else if(this.status === REJECTED){
failCallback(this.reason);
}else{
// 异步的场景,代表当前状态为等待状态,但是这里只实现了一层then,如果存在链接多个then,后面的会覆盖前面的。
this.successCallback = successCallback;
this.failCallback = failCallback;
}
}
}
实现then方法链式调用多次
const PEMDING = 'pending';//等待
const FULFILLED = 'fulfilled';//成功
const REJECTED = 'rejected';//失败
class MyPromise{
constructor(execurot){
execurot(this.resolve,this.reject);//立即执行的执行器
}
// promise 状态
status = PEMDING;
// 成功之后的值
value = undefined;
// 失败的原因
reason = undefined;
// 成功回调
successCallback = [];
// 失败回调
failCallback = [];
resolve = value => { //箭头函数是为了避免 this 指向出问题 要指向class
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 讲状态改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// 判断成功回调是否存在
// this.successCallback && this.successCallback(this.value);
while(this.successCallback.length) this.successCallback.shift()(this.value);
}
reject = reason => {
// 如果状态不为pending 阻止程序向下执行
if(this.status !== PEMDING) return;
// 将状态改为失败
this.status = REJECTED;
// 保存失败之后的原因
this.reason = reason
// 判断失败回调是否存在
// this.failCallback && this.failCallback(this.reason);
while(this.failCallback.length) this.failCallback.shift()(this.reason);
}
then(successCallback,failCallback){
// 判断状态
if(this.status === FULFILLED){
successCallback(this.value)
}else if(this.status === REJECTED){
failCallback(this.reason);
}else{
//代表当前状态为等待状态,此时是以一个数组的形式存放所有的回调。
this.successCallback.push(successCallback)
this.failCallback.push(failCallback);
}
}
}
let promise = new Promise ((resolve,reject)=>{
resolve('成功')
})
promise.then(()=>{},()=>{}).then(()=>{},()=>{}).then(()=>{},()=>{})
这种情况是没有考虑到then的回调函数中如果返回的还是promise的场景。笔者有空的时候,会继续完善promise 的实现。争取实现一个符合promiseA+规范的。