从入门到手写Promise

406 阅读12分钟

Promise是什么?

抽象表达:

Promise是JS中进行异步编程的新方案(旧方案是回调地狱)。

具体表达:

(1)、从语法上来说,Promise是一个构造函数。

(2)、从功能上来说,Promise是用来封装一个异步操作并可以获取其结果的对象。

为什么要用Promise?

(1)、指定回调函数的方式更加灵活:旧的方案必须在启动异步任务前指定回调函数。

  • Promise可以先启动异步任务,返回Promise对象后,再给Promise对象绑定回调函数。
  • Promise甚至可以在异步任务结束后指定。(得到一个Promise对象说明异步任务启动了,启动很快,但是完成需要时间)
    (2)、支持链式调用,可以解决回调地狱的问题:回调地狱就是回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件,这样的写法不便于阅读,也不便于异常处理。

Promise的状态改变

Promise改变状态只能从pending变为resolved或者从pending变为rejected这两种,并且只能改一次,无论成功还是失败,都会返回一个结果。成功的结果数据称为value,失败的结果数据称为reason。

改变promise的状态和指定回调函数谁先谁后?

都有可能,正常情况下是先指定回调再改变状态,但也可先改变状态再指定回调,只有状态改变,回调函数才会执行。那么什么时候才能的得到数据?

①如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据

②如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据

先改状态再指定回调函数

const p = new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('定时器执行')
            resolve(Date.now())
        }, 1000);
})
setTimeout(() => {
      p.then(value => {
           console.log('成功的回调',value)
      })
}, 2000);
    //打印结果: 
        //定时器执行 
       //成功的回调 1591627205718
      //改状态是在1秒后,指定回调函数延迟更长的时间

先指定回调函数再改状态

const p = new Promise((resolve,reject) => {
        setTimeout(() => {
            console.log('定时器执行')
            resolve(Date.now())//后改变状态(同时指定数据),然后异步执行回调函数
        }, 1000);
})
 p.then(value => { //先指定回调函数,但是resolve未执行,状态还没改变,所以回调函数会保存起来
       console.log('成功的回调',value)
 })
    //打印结果: 
       //定时器执行 
      //成功的回调 1591627539711

Promise的同步和异步

  1. promise中的excutor函数:执行器:(resolve,reject) => {},会在Promise内部立即同步回调,执行器中异步任务被放入宏/微队列中等待执行。
  2. .then()也是同步的,.then()和回调函数不是一回事,
  • .then()是去指定回调函数的。同步改的状态,同步指定的回调函数,那么执行回调函数的条件就满足了,按理来说我们应该马上执行回调函数。但是.then()中的回调函数(value=>{},reason=>{}),是异步的,即使条件已经满足也不是立即执行的。
  • .then()中的同步操作是直接在.then()里面return里执行(也就是value=>{}这个大括号里面执行),异步操作就return一个新的Promise(promise封装异步操作)。

promise.then()返回的新promise的结果由什么决定?

(1)、简单描述:由then()指定的回调函数决定

(2)、详细描述:

a.如果PromiseA返回的是另一个新的PromiseB,PromiseB的结果就是PromiseA的结果
b.如果PromiseA返回是非Promise的任意值,PromiseA变为resolved,value为返回值。
c.如果抛出异常,PromiseA变为rejected,reason为抛出异常的原因。

注意事项:下一个then的结果,和上一个Promise执行value=>{}成功的回调还是reason()=>失败的回调没有关系,而是由value()=>{}或reason()=>{}执行的结果决定。

  new Promise((resolve,reject) => {
            reject(1)
        }).then(
            value => console.log('成功的1',value),
            reason => {
                console.log('失败的1',reason)
                return new Promise((resolve,reject) => {
                  resolve(2)
                })
            }
        ).then(
           value => {
            console.log('成功的2',value)
            return 3
           },
           reason => console.log('失败的2',reason)
        ).then(
           value=> {
               console.log('成功的3',value)
               throw Error('Error')
           },
           reason=> console.log('失败的3',reason)
        ).then(
            value => console.log('成功的4',value),
            reason => console.log('失败的4',reason)
        )
        //结果:
          失败的1 1
          成功的2 2
          成功的3 3
          失败的4 Error: Error

以上代码说明:

  • 执行reject(1),第一个.then()变为rejected,返回新的Promise,第二个.then()变为resolved,value值就是上个Promise执行返回的结果,说明上述a。同时也说明注意事项的内容。
  • 第二个.then()返回非Promise任意值,第三个.then()变为resolved,value就是return的3,说明上述b。
  • 第三个.then()抛出异常,第四个.then()变为rejected,说明上述c 。

Promise如何串连多个任务?

(1)、Promise的then()返回一个新的Promise,可以形成then的链式调用。
(2)、通过then的链式调用串连多个同步/异步任务。

Promise异常传透(穿透)?

(1)、当使用promise的then链式调用时,可以在最后指定失败的回调;
(2)、当前任何操作出了异常,都会传到最后失败的回调中处理。

具体点:比如说你中上一个Promise中执行了reject(1),但是后面的then()中并没有写对失败处理的回调(其实相当于写了reason =>{throw reason}才可以把错误向下抛),就会通过.then()一层一层传透过去,直到最后没找到处理失败的回调就会报错:Uncaught (in promise) 1。但是如果你在链式调用中写了.catch(),那么失败的回调就会在这里被捕获,不再往下传透。

注意:then 和 catch 期望接收函数做参数,如果非函数就会发生 Promise 穿透现象,打印的是上一个 Promise 的返回。

new Promise((resolve,reject) => {
            reject(1)
        }).then(
            value => console.log('成功的1',value),
        ).catch(
           reason => {
            console.log('reason',reason)
            // throw reason   // ------>在这里如果不抛异常或者返回失败的Promise,后面的then()就会进入成功回调
            // return Promise.reject(reason)
          }
        ).then(
           value => console.log('成功的1',value),
           reason => console.log('失败的1',reason)
        )
      // 结果:reason 1
     //       成功的1 undefined

中断Promise链?

上面代码中,在catch()里面如果不抛出异常或者返回失败的Promise,后面的then会进入成功的回调,如果抛了异常或返回了失败的Promise,后面的then就会进入失败的回调。如果不想再进入后面的then呢?该怎么办?

解决办法:返回一个pending状态的Promise。 return new Promise(()=>{})

这样为什么后面的就不执行了呢?因为一旦返回新的Promise,这个新的Promise的结果就决定了后面的then执行成功或者失败的回调。但是这个新的Promise一直处于pending阶段,无法获取结果。实现了中断Promise链。

简述Promise A+规范

术语:

1)promise是包含了then方法的对象或函数;
2)thenable是包含了then方法的对象或函数;
3)value可以是任意值;
4)exception是由throw抛出的值;
5)reason描述promise被拒绝的原因;
要求:

  • promise状态

1)promise必须处于pending,fulfilled或rejected之一;
2)如果是pending,可以变为fulfilled或者rejected状态;
3)如果是fulfilled或者rejected状态,不能再改变。fulfilled必须有一个不可变的值,rejected必须有一个不可变的原因;

  • then方法

1)promise必须提供一个then方法来获取其值和原因;
2)then方法接受2个参数:onFulfilled,onRejected;
3)onFulfilled和onRejected都是可选参数,如果二者不是一个函数,则忽略之;
4)如果onFulfilled是个函数,它必须在promise fulfilled后调用(且只能调用一次),且promise的value为其第一个参数;
5)如果onRejected是个函数,它必须在promise rejected后调用(且只能调用一次),且promise的reason为其第一个参数;
6)在执行上下文堆栈仅包含平台代码之前,不得调用onFulfilled或onRejected(对于then方法来说它是一个异步过程);
7)onFulfilled和onRejected必须被当做函数被调用(即非实例化调用,这样函数内部this非严格模式下指向window);
8)then方法可以被多次调用,当promise fulfilled(或rejected)后,所有onFulfilled(或onRejected)必须按照其注册顺序执行;
9)then必须返回一个promise。promise2 = promise1.then(onFulfilled, onRejected);

  • 如果onFulfilled或onRejected返回了值x,则promise2状态为fulfilled,返回值为x;
  • 如果onFulfilled或onRejected抛出异常e,则promise2状态为rejected,返回值为e;
  • 如果onFulfilled不是一个函数且promise1已经fulfilled,promise2必须以promise1的值fulfilled;
  • 如果onReject不是一个函数且promise1已经rejected,则promise2必须以相同的reason被rejected。
  • promise解决过程 promise解决过程是一个抽象的操作,其需输入一个promise和一个值,表示为[[Resolve]] (promise,x) 这句话意思:把promise resolvel同时传入x作为值。
promise.then(function(x) {
	console.log('会执行这个函数,同时传入x变量的值',x);
})

1)如果x有then方法,且看上去像一个promise,解决程序尝试使promise接受x的状态,否则其用x的值来执行promise。
2)如果promise和x指向同一个对象,以TypeError为原因拒绝执行promise
3) 如果x为promise

  • 如果x处于pending状态,promise需保持为等待状态直至被执行或者拒绝
  • 如果x处于onFulfilled,用相同的值resolve promise
  • 如果x处于onRejected,用相同的值reject promise 4)如果x为Object或function(不常见)
  • 首先尝试执行x.then
  • 如果取x.then的值时抛出错误e,则以e为据因拒绝promise
  • 如果then是函数,将x作为函数的作用域this调用,传递2个回调函数作为参数(resolvePromise,rejectPromise)
  • 如果resolvePromise以值y为参数被调用,则运行[[Resolve]] (promise,y)
  • 如果rejectPromise以据因r为参数被调用,则以据因r拒绝promise
  • 如果resolvePromise和rejectPromise均被调用,或者同一参数被调用多次,则优先采用首次调用并忽略其它调用
  • 如果调用then方法抛出异常,如果resolvePromise或rejectPromise已经被调用,则忽略,否则以e为拒因拒绝promise
  • 如果then不为函数,以x为参数将promise变成onFulfilled状态
    5)如果x不为对象或者函数(是number或者string),以x为参数将promise变成onFulfilled状态

手写Promise

(function() {
    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';

    function Promise(excutor) {
        this.status = PENDING;
        this.data = undefined;
        this.callbacks = [];
        const self = this;

        function resolve(value) {
            if(self.status !== PENDING) return;
            this.status = RESOLVED;
            this.data = value;
            if(self.callbacks.length > 0) {
                setTimeout(() => {
                 self.callbacks.forEach(callbackObj => {
                     callbackObj.onResolved(value)
                 })   
                });
            }
        }
        
        function reject(reason) {
            if(self.status !== PENDING) return;
            this.status = REJECTED;
            this.data = reason;
            if(self.callbacks.length > 0) {
                self.callbacks.forEach(callbackObj => {
                    callbackObj.onRejected(reason)
                })
            }
        }

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

    Promise.resolve = function(value) {
        return new Promise((resolve, reject) => {
            if(value instanceof Promise) {
                value.then(resolve, reject);
            } else {
                resolve(value);
            }
        })
    }

    Promise.reject = function(reason) {
        return new Promise((resolve, reject) => {
            reject(reason);
        })
    }

    Promise.all = function(promises) {
        const values = new Array(promises.length); // 指定数组长度
        let count = 0;
        return new Promise((resolve, reject) => {
            promises.forEach((p,index) => {
                p.then(
                    value => {
                        count++;
                        values[index] = value;
                        if(count === promises.length) {
                            resolve(values);
                        }
                    },
                    reason => {
                        reject(reason)
                    }
                )
            })
        })
    }

    Promise.race = function(promises) {
        return new Promise((resolve, reject) => {
            promises.forEach((p, index) => {
                p.then(
                    value => {
                        resolve(value);
                    },
                    reason => {
                        reject(reason);
                    }
                )
            })
        })
    }

    Promise.resolveDelay = function(value, delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                if(value instanceof Promise) {
                    value.then(resolve, reject);
                } else {
                    resolve(value);
                }
            }, delay);
        })
    }

    Promise.rejectDelay = function(reason, delay) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                reject(reason)
            }, delay);
        })
    }
    /**
     * 将执行回调函数抽取成一个函数,需要改变then返回的promise的状态,结果有三种情况
     * 1:抛出异常,我们用try...catch捕获,
     * 2:如果返回了一个promise(判断结果是不是promise的实例,调用then去执行
     * 3:如果返回非promise,值就是result
   
     * 如果是pending状态,说明先指定的回调,将回调函数加入队列稍后执行
     * 如果是resolve状态或者rejected,说明状态改变,异步执行队列的函数,
     */
    Promise.prototype.then((onResolved, onRejected) => {
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
        const self = this;
        return new Promise((resolve, reject) => {
         // 提取handle函数,用于改变新的promise的状态
            function handle(callback) {
                try {
                    const result = callback(self.data);
                    if(result instanceof Promise) {
                        result.then(resolve, reject)
                    } else {
                        resolve(result);
                    }
                } catch (error) {
                    reject(error)
                }
            }
            if(self.status === PENDING) {
                self.callbacks.push({ // 存回调,改状态,
                    onResolved() {
                        handle(onResolved);
                    },
                    onRejected() {
                        handle(onRejected);
                    }
                })
            } else if(self.status === RESOLVED) {
                setTimeout(() => {
                    // 异步执行回调函数
                   handle(onResolved)
                });
            } else {
                setTimeout(() => {
                    handle(onRejected)
                });
            }
        })
    })
    window.Promise = Promise;
})(window);

最后来几道常见面试题吧

题1:下面三个有哪些不同

const promise = new Promise(function(resolve, reject){
    resolve(1);
})

// 1
promise.then(() => {
  return Promise.resolve(2);
}).then((n) => {
  console.log(n)
});

// 2
promise.then(() => {
  return 2
}).then((n) => {
  console.log(n)
});

// 3
promise.then(2).then((n) => {
  console.log(n)
});
// 结果:2 1 2
分析:1:第一个then里面返回一个新的promise,后面的then在下一个事件循环机制才会去执行
     2return 一个2,它不用等下一个事件循环。
     3:then 和 catch 期望接收函数做参数,若非函数会发生 Promise 穿透,打印上一个 Promise 的返回。

题2:

let a;
const b = new Promise((resolve, reject) => {
  console.log('promise1');
  resolve();
}).then(() => {
  console.log('promise2');
}).then(() => {
  console.log('promise3');
}).then(() => {
  console.log('promise4');
});

a = new Promise(async (resolve, reject) => {
  console.log(a);
  await b;
  console.log(a);
  console.log('after1');
  await a
  resolve(true);
  console.log('after2');
});

console.log('end');
// 结果:promise1, undefined, end, promise2, promise3, promise4, Promise:{<pending>}, after1
分析:前面的比较简单就不解释了,有一点比较难理解的是:从 await a 开始,a 是必须等待 Promise 的状态改变才会继续往下执行,可 a 的状态是一直得不到更改的,所以无法执行下面的逻辑。只要在 await a 上面加一行 resolve() 就能让后面的 after 2 得到输出。

题3:如何取消promise?

function wrap(p) {
  let resol = null;
  let abort = null;
  
  const p1 = new Promise((resolve, reject) => {
    resol = resolve;
    abort = reject;
  });
  p1.abort = abort;
  p.then(resol, abort);
  return p1;
}

const newPromise = wrap(promise);
newPromise.then(res => console.log)
newPromise.abort()
分析:promise 缺陷:无法得知执行到哪,也无法取消,只能被动的等 resolve 或者 reject 执行或者抛错。
思路:外部包裹一层 Promise,并对外提供 abort 方法,这个 abort 方法可以用来 reject 内部的 Promise 对象。

题4:给你若干promise对象,如何保证顺序输出?

// 题:
const delay = time => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, time);
    })
}
function order(promises) {
}
const ajaxs = [
    () => delay(200).then(() => {  console.log('a'); return 'a'}),
    () => delay(600).then(() => {  console.log('b'); return 'b'}),
    () => delay(1000).then(() => {  console.log('c'); return 'c'}),
    () => delay(1200).then(() => {  console.log('d'); return 'd'}),
    () => delay(1600).then(() => {  console.log('e'); return 'e'}),
]
order(ajaxs);
// 答案
function order(promises) {
    let result = [];
    let promise = Promise.resolve();
    promises.forEach(item => {
        promise = promise.then(item);
        result.push(promise);
    })
    return Promise.all(result).then(data => {
      console.log(data);
    });
}

题5:如何并行发送指定数量的请求?

const delay = function delay(interval) {
     return new  Promise((resolve, reject) => {
		 setTimeout(() => {
			resolve(interval);
         }, interval);
     })
}
let tasks = [
      () => { return delay(1000)},
      () => { return delay(1003)},
      () => { return delay(1005)},
      () => { return delay(1002)},
]
function createRequest(tasks, limit, callback) {
      if(typeof limit === 'function') {
          callback = limit;
	      limit = 2;
	  }
      if(typeof limit !== 'number') limit = 2;
      if(typeof callback !== 'function') callback = function() {}
      class TaskQueue {
          constructor() {
             this.index = 0;
             this.queue = [];
             this.results = [];
          }
          pushTask(task) {
             this.queue.push(task);
             this.next();
          }
          next() {
             while(this.index < limit && this.queue.length) {
                this.index++;
                const task = this.queue.shift();
                task().then(result => {
                     this.results.push(result);
                }).finally(() => {
                    this.index--;
                    this.next();
                })
             }
             if(this.index === 0) callback(this.results);
         }
     }
     const taskQueue = new TaskQueue;
     tasks.map(task => taskQueue.pushTask(task));
}
createRequest(tasks, 2, (results) => {console.log(results)})

题6

const promise1 = new Promise((resolve, reject) => { reject() }) 
promise1.then(null, function() {return 123})
        .then(null, null)
        .then(
            (value) => { console.log('已经完成', value) },
            (reason) => { console.log('已经失败', reason) }
        )
// 已经完成 ,123  then中onFulfilled,onRejected不是一个函数,可以被忽略,promise1.then返回一个值123,进入onFulFilled状态。

结语

关于promise的介绍就到这啦,需要小伙伴们耐心看完。如有不对的地方,肯请指出哦😸!