promise的前世今生

361 阅读12分钟

前言

promise的前世今生:

javascript的异步编程 —— callback —— Promise —— Generator(yield)—— co —— async await

在JavaScript中,遇到setTimeout,ajax,事件这些需要等待的情况,都是异步执行的,因为他不会一直等着,这样会阻塞后续代码的执行,例如:

function a(){
  setTimeout(function(){
    console.log(1)
  },0)
  
}
function b(){
  console.log(2)
}
a()
b()

执行结果:
2
1

如果我们一定要先打印 1 怎么办?

function a(){
  setTimeout(function(){
    console.log(1)
    b()
  },0)
  
}
function b(){
  console.log(2)
}
a()

执行结果:
1
2

这就叫做回调函数,callback,这是简单的例子,在实际开发过程中,如果遇到复杂的逻辑,你可能要不停的回调,回调,回调,这样的弊端也是显而易见的,于是,es6规范,promise诞生了,下面我们就来看看promise的基本用法。

1、Promise的基本用法

let promise=new Promise((resolve,reject)=>{
    let flag=false
    if(flag){
        resolve('成功了')
    }else{
        reject('失败了')
    }
})
promise.then(data=>{
    console.log("s",data)
},err=>{
    console.log("e",err)
})

我们看到,它其实就是个构造函数,为了深度理解promise的原理,我们来模拟一下,我把它命名为P:

class P{
    constructor(executor){
        //promise的三个状态,resolved、rejected、penging,
        //默认是等待态
       this.status='pending';
       //参考:https://promisesaplus.com/  
       this.value=undefined;
       this.reason=undefined;

       //成功的回调
       this.onResolvedCallbacks=[]
       //失败的回调
       this.onRejectedCallbacks=[]

       let resolve=(data)=>{
       //状态是“等待态”才能转换状态,执行方法,因为成功了就不能失败,失败了就不能成功
           if(this.status === 'pending'){  
               this.value=data;
               this.status='resolved'
               this.onResolvedCallbacks.forEach(fn=>fn())
           }
       }
       let reject=(reason)=>{
           if(this.status === 'pending'){
               this.reason=reason
               this.status='rejected'
               this.onRejectedCallbacks.forEach(fn=>fn())
           }
       }
       try {
        executor(resolve,reject)
       }catch(e){
           reject(e)
       }
      
    }
    then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的
       //状态为成功的时候就执行成功的方法,并且把值传给他
       if(this.status == 'resolved'){
        onFulFilled(this.value)
       }
       //失败的状态,就把失败的原因传出来
       if(this.status == 'rejected'){
        onRejected(this.reason)
       }
    }
}

module.exports=P

试用一下:

let P=require('./myPromise')

let p=new P((r,j)=>{
    console.log("1")
  setTimeout(function(){
      r('3') //这里不会执行,应为状态是“等待态”,既不成功,也不失败
  },1000)
})
p.then(data=>{
    console.log(data)
},err=>{
    console.log(err)
})
//执行
1

执行结果中我们看到 setTimeout 中的代码没有执行,那么我们接着写如果状态为“等待态”的时候该怎么执行:

then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的
       //状态为成功的时候就执行成功的方法,并且把值传给他
       if(this.status == 'resolved'){
        onFulFilled(this.value)
       }
       //失败的状态,就把失败的原因传出来
       if(this.status == 'rejected'){
        onRejected(this.reason)
       }
       //等待态,既没有成功,也没有失败
       if(this.status == 'pending'){
           //存放成功的回调
           this.onResolvedCallbacks.push(()=>{
               onFulFilled(this.value)
           })
           //存放失败的回调
           this.onRejectedCallbacks.push(()=>{
               onRejected(this.reason)
           })
       }
    }

再执行:

let P=require('./myPromise')

let p=new P((r,j)=>{
    console.log("1")
  setTimeout(function(){
      r('3') 
  },1000)
})
p.then(data=>{
    console.log("success:"+data)
},err=>{
    console.log("failed:"+err)
})

打印结果:
1
success:3

2、promise的链式调用

说起链式调用,我们可以联想到 jquery,jquery 能够实现链式调用是因为它每次调用后都返回了this,实例本身,而 promise 呢,不能靠 this ,却是在 then 方法里返回的一个新的 promise ,一个道理的。 代码如下:

let promise=new Promise((resolve,reject)=>{
   resolve('1')
})
promise.then(data=>{
   console.log("data1:",data)    //1
   return new Promise((resolve,reject)=>{
       resolve("2")
   })
},err=>{
    console.log("err1:",err)
}).then(data=>{
    console.log("data2:",data)    //2
    return new Promise((resolve,reject)=>{
        //resolve("3")
        reject('failed')
    })
},err=>{
    console.log("err2:",err)
}).then(data=>{
    console.log("data3:",data)    //
    return new Promise((resolve,reject)=>{
        resolve("4")
    })
},err=>{
    console.log("err3:",err)
}).then(data=>{
    console.log("data4:",data)    //undefined
    return new Promise((resolve,reject)=>{
        resolve("5")
    })
},err=>{
    console.log("err4:",err)
}).then(data=>{
    console.log("data5:",data)
})


打印结果:
data1: 1
data2: 2
err3: failed
data4: undefined
5

由此可见:

1)promise 链中,每个then返回的新 promise 都是独立的,有成功的回调和失败的回调;
2)每个then的参数都来源于上一个 promise 的执行,如果上一个 promise 成功了,那么成功的 data 就会传到下一个promise的成功回调中,如果上一个 promise 失败了,那么它会走失败的回调,此时下一个 promise 的失败回调中会接收到相应的参数,而成功的回调中接收到的是 undefined ,但是promise链继续往下执行,不受任何影响;
3)如果根据实际需求,我们只需要按顺序执行方法,也可以不传参哦

如果我们换个写法:

let promise=new Promise((resolve,reject)=>{
    resolve('1')
 })
 promise.then(data=>{
    console.log("data1:",data)    //1
    return Promise.resolve("2")
 })
 .then(data=>{
     console.log("data2:",data)    //2
     return Promise.reject('failed')
 })
 .then(data=>{
     console.log("data3:",data)    //undefined
     return Promise.resolve("4")
 })
 .then(data=>{
     console.log("data4:",data)    //4
     return Promise.resolve("5")
 })
 .then(data=>{
     console.log("data5:",data)
 })
 .catch(e=>{
    console.log('e: ', e);
})

打印:
data1: 1
data2: 2
e:  failed
let promise=new Promise((resolve,reject)=>{
    resolve('1')
 })
 promise.then(data=>{
    console.log("data1:",data)    //1
    return Promise.resolve("2")
 })
 .then(data=>{
     console.log("data2:",data)    //2
     return Promise.reject('failed')
 })
 .catch(e=>{
    console.log('e: ', e);
})
 .then(data=>{
     console.log("data3:",data)    //undefined
     return Promise.resolve("4")
 })
 .then(data=>{
     console.log("data4:",data)    //4
     return Promise.resolve("5")
 })
 .then(data=>{
     console.log("data5:",data)
 })
 
 打印:
 data1: 1
 data2: 2
 e:  failed
 data3: undefined
 data4: 4
 data5: 5

由此我们可以得出:如果某个 then 方法里面的 promise 状态改变为了 rejected,则promise 方法连会跳过后面的 then 直接执行 catch。

接下来,我们继续完成我们自己的promise:

function resolvePromise(promise2,x,resolve,reject){
   //判断x 是不是promise
   //规范里规范了一段代码,这个代码可以实现我们的promise和别人的promise进行交互
   if(promise2 === x){  
       //不能自己等待自己完成
       return reject(new TypeError('循环引用'))
   }
   // x 不能是null   而是对象或者 函数
   if(x !== null && (typeof x === 'object' || typeof x === 'function' )){
       let called;  //防止成功后调用失败
       try{  //防止取then时出现异常 Object.defineProperty
          let then = x.then   // 取x的then方法 {then:{}}
          if(typeof then === 'function'){ //如果then是函数 就认为它是promise
                // call 第一个参数是this ,后面的是成功的回调和失败的回调
                then.call(x, y => { // 如果y(成功的回调)是promise就继续递归解析promise
                    if(called) return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                }, r => { // 只要失败了就失败了
                    if (called) return;
                    called = true;
                    reject(r);
                });
          }else{
              // then是一个普通对象,就直接成功即可
              resolve(x);
          }
       }catch(e){ // 失败
            if (called) return;
            called = true;
            reject(e);
       }
   }else { 
       resolve(x); // x就是一个普通值 如:x = 123     
     }
}


class P{
    constructor(executor){
        //promise的三个状态,resolved、rejected、penging,
        //默认是等待态
       this.status='pending';
       //参考:https://promisesaplus.com/  
       this.value=undefined;
       this.reason=undefined;

       //成功的回调
       this.onResolvedCallbacks=[]
       //失败的回调
       this.onRejectedCallbacks=[]

       let resolve=(data)=>{
           if(this.status === 'pending'){  //状态是“等待态”才能转换状态,执行方法,因为成功了就不能失败,失败了就不能成功
               this.value=data;
               this.status='resolved'
               this.onResolvedCallbacks.forEach(fn=>fn())
           }
       }
       let reject=(reason)=>{
           if(this.status === 'pending'){
               this.reason=reason
               this.status='rejected'
               this.onRejectedCallbacks.forEach(fn=>fn())
           }
       }
       try {
        executor(resolve,reject)
       }catch(e){
           reject(e)
       }
      
    }
    then(onFulFilled,onRejected){ //两个回调函数:成功的、失败的

        let promise2;

       //状态为成功的时候就执行成功的方法,并且把值传给他
       if(this.status == 'resolved'){

            promise2 = new Promise((resolve, reject) => {
                // 成功的逻辑 失败的逻辑
                let x = onFulFilled(this.value);
                // 看x是不是promise 如果是promise 取他的结果 作为promise2,成功的结果
                // 如果要是返回一个普通值 作为promise2,成功的结果
        
                // resolvePromise可以解析x和promise2之间的关系
                resolvePromise(promise2, x, resolve, reject);
            });
       }
       //失败的状态,就把失败的原因传出来
       if(this.status == 'rejected'){
            promise2 = new Promise((resolve, reject) => {
                let x = onRejected(this.reason);
                resolvePromise(promise2, x, resolve, reject)
            });
       }
       //等待态,既没有成功,也没有失败
       if(this.status == 'pending'){
           //存放成功的回调
           promise2 = new Promise((resolve, reject) => {
            this.onResolvedCallbacks.push(() => {
              let x = onFulFilled(this.value);
              resolvePromise(promise2, x, resolve, reject)
            });
            // 存放失败的回调
            this.onRejectedCallbacks.push(() => {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            });
          })
       }
       return promise2; // 调用then后返回一个新的promise
    }
}

module.exports=P

我们知道我们的回调函数都是异步执行,为了模拟异步,我们暂且用setTimeout模拟延迟执行

 then(onFulFilled, onRejected) {
    // 解决onFulFilled,onRejected没有传的问题
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err; };
    let promise2;
    if (this.status === 'resolved') {
      promise2 = new Promise((resolve, reject) => {
        // 成功的逻辑 失败的逻辑
        setTimeout(() => {
          try {
            let x = onFulFilled(this.value);
            // 看x是不是promise 如果是promise 取他的结果 作为promise2,成功的结果
            // 如果要是返回一个普通值 作为promise2,成功的结果

            // resolvePromise可以解析x和promise2之间的关系
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
    }
    if (this.status === 'rejected') {
      promise2 = new Promise((resolve, reject) => {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e);
          }
        }, 0);
      });
    }
    // 当前既没有完成 也没有失败
    if (this.status === 'pending') {
      // 存放成功的回调
      promise2 = new Promise((resolve, reject) => {
        this.onResolvedCallbacks.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);
        });
      })
    }
    return promise2; // 调用then后返回一个新的promise
  }

测一下我们的代码

let P=require('./myPromise')
let promise=new P((resolve,reject)=>{
    resolve('1')
 })
 promise.then(data=>{
    console.log("data1:",data)    //1
    return new Promise((resolve,reject)=>{
        resolve("2")
    })
 },err=>{
     console.log("err1:",err)
 }).then(data=>{
     console.log("data2:",data)    //2
     return new Promise((resolve,reject)=>{
         //resolve("3")
         reject('failed')
     })
 },err=>{
     console.log("err2:",err)
 })
 .then(data=>{
     console.log("data3:",data)    //undefined
     return new Promise((resolve,reject)=>{
         resolve("4")
     })
 },err=>{
     console.log("err3:",err)
 }).then(data=>{
     console.log("data4:",data)    //4
     return new Promise((resolve,reject)=>{
         resolve("5")
     })
 },err=>{
     console.log("err4:",err)
 }).then(data=>{
     console.log("data5:",data)
 }).catch(e=>{
     console.log('e: ', e);
 })
 
 打印结果:
 data1: 1
 data2: 2
 err3: failed
 data4: undefined
 data5: 5

看下面这段:

let promise=new Promise((resolve,reject)=>{
   resolve('1')
})
promise.then(data=>{
   console.log("data1:",data)    //1
},err=>{
    return '11'
    console.log("err1:",err)
}).then(data=>{
    console.log("data2:",data)    
    return new Promise((resolve,reject)=>{
        //resolve("3")
        reject('failed')
    })
},err=>{
    console.log("err2:",err)
})

执行结果:
data1: 1
data2: undefined
let promise=new Promise((resolve,reject)=>{
   resolve('1')
})
promise.then(data=>{
   console.log("data1:",data)    //1
   return '11'
},err=>{
    console.log("err1:",err)
}).then(data=>{
    console.log("data2:",data)    
    return new Promise((resolve,reject)=>{
        //resolve("3")
        reject('failed')
    })
},err=>{
    console.log("err2:",err)
})

执行结果:
data1: 1
data2: 11

我们看到:当我们的 then 返回的是普通值的时候,如:11,那么下一个 then 会在成功的回调中接收它的值 区别是:如果 上一个 then 在成功回调中返回,那么下一个就能接收的他的值,如果在失败的回调中返回,那么接收的就是 undefined 了。

如果返回的是新的 promise对象,那么下一个 then 就会根据成功回调和失败回调来执行响应的代码了。

好了,到此为止,就把 promise 的链式调用写完了,接下来看看 promise 的几个方法:

Promise.resolve()、Promise.reject()、Promise.race()、Promise.all()。 Promise.resolve()和Promise.reject() 都返回确定了状态的promise对象,之后可调用then、catch方法,可以创建一个确定状态的promise对象,用于将一些非promise 对象转换为promise 。

P.resolve=function(val){
    return new Promise((resolve,reject)=>{
        resolve(val)
    })
}
P.reject=function(val){
    return new Promise((resolve,reject)=>{
        reject(val)
    })
}
P.race=function(promises){
    //promises  数组
    return new Promise((resolve,reject)=>{
       for(let i=0;i<promises.length;i++){
           promises[i].then(resolve,reject)
       }
    })
}
P.all=function(promises){
    return new Promise((resolve,reject)=>{
        let arr=[];
        let i=0; //为了保证获取全部成功,来设置索引
        function processData(index,data){
            arr[index]=data;
            i++;
            if(i===promises.length){
                resolve(arr)
            }
        }
        for(let i=0;i<promises.length;i++){
            //成功的话才放入数组
            promises[i].then(data=>{
                processData(i,data);
            }, reject);
        }
    })
}

deferred对象

jQuery的deferred对象详解 http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html

Promise.deferred = Promise.defer = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  })
  return dfd;
}

3、生成器Generators/ yield

Generators是es6的新特性,可以帮助我们更好的控制一个函数的执行,他有两个特征:

  1. function关键字与函数名之间有一个星号;
  2. 函数体内部使用yield语句,定义不同的内部状态(yield语句在英语里的意思就是“产出”)。

如果在函数执行到某的地方的时候,你想要看它的返回值,那么,只要用yield, 就会把yield后面的值返回,这个时候,函数是暂停的,你执行next()方法,可以看到返回的内容,返回的内容是个对象,有两个属性:value和done,value就是返回值,done是布尔值,false表示后面还有yield,true表示已经没有yield 了,每next()一次,它才走一步,直到走完,还是看例子吧:

function* g() {
  yield '1';
  yield '2';
}
var a = g();

a.next()
{value: "1", done: false}
a.next()
{value: "2", done: false}
a.next()
{value: undefined, done: true}
a.next()
{value: undefined, done: true}

也可以用return返回最后一个值

function* g() {
  yield '1';
  yield '2';
  return 'end';
}
var a = g();

执行并打印结果:

a.next()
{value: "1", done: false}
a.next()
{value: "2", done: false}
a.next()
{value: "end", done: true}
a.next()
{value: undefined, done: true}

4、co

co.js 是 TJ 大神基于es6的 generator 编写的 JavaScript 异步解决方案的库,用于让异步的代码 "同步化",相当于generator函数的一个自动执行器。

co.js 将一个异步函数thunk化

原函数
fs.readFile(path,callback)
thunk化后函数
let readFile = (path) => (callback) => fs.readFile(path, callback)
var co = require('co');
function* g() {
  var a=yield Promise.resolve(()=>{console.log(1)})
  var b=yield Promise.resolve(2);
  console.log(a)
  console.log(b)
}
var a = g();
//a.next()
co(a);

执行:
[Function]
2

需要注意的是,yield 后面必须是 promise, generator, array, or object 若想了解 co库 原理,请阅读阮一峰老师的:

http://www.ruanyifeng.com/blog/2015/05/co.html

4、async await

ES7 提出的async 函数是 Generator 函数的语法糖,想较于 Generator,Async 函数的改进在于下面四点:

内置执行器。Generator 函数的执行必须依靠执行器,而 Aysnc 函数自带执行器,调用方式跟普通函数的调用一样

更好的语义。async 和 await 相较于 * 和 yield 更加语义化

更广的适用性。co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise对象。而 async 函数的 await 命令后面则可以是 Promise 或者 原始类型的值(Number,string,boolean,但这时等同于同步操作)

返回值是 Promise。async 函数返回值是 Promise 对象,比 Generator 函数返回的 Iterator 对象方便,可以直接使用 then() 方法进行调用

使用:

async 函数返回一个 Promise 对象

async 函数内部 return 返回的值。会成为 then 方法回调函数的参数。

async function  f() {
    return 'hello world'
};
f().then( (v) => console.log(v))

打印结果:
 hello world

如果 async 函数内部抛出异常,则会导致返回的 Promise 对象状态变为 reject 状态。抛出的错误而会被 catch 方法回调函数接收到。

async function e(){
    throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));

打印:
Error: error

async 函数返回的 Promise 对象,必须等到内部所有的 await 命令的 Promise 对象执行完,才会发生状态改变

也就是说,只有当 async 函数内部的异步操作都执行完,才会执行 then 方法的回调。

const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
    await delay(1000);
    await delay(2000);
    await delay(3000);
    return 'done';
}

f().then(v => console.log(v)); 

打印结果:
等待6s后才输出 'done'

正常情况下,await 命令后面跟着的是 Promise ,如果不是的话,也会被转换成一个 立即 resolve 的 Promise 如下面这个例子:

async function  f() {
    return await 1
};
f().then( (v) => console.log(v)) 

打印结果:
 1

如果返回的是 reject 的状态,则会被 catch 方法捕获。

学海无涯苦作舟,以此共勉!

参考: http://schifred.iteye.com/blog/2317978 https://www.cnblogs.com/starthust/p/3876458.html https://www.cnblogs.com/ranjianxi/p/6808147.html http://www.ruanyifeng.com/blog/2015/05/co.html https://segmentfault.com/a/1190000010244279