likes
comments
collection
share

Promise梳理

作者站长头像
站长
· 阅读数 25

什么是Promise?

Promise是异步问题同步化的解决方案,不会阻塞同步代码的执行。通过resolve和reject改变Promise当前的状态,then获取异步请求之后的结果,从而避免回调地狱。

  • 语法:Promise是ES6提供的构造函数,通过new创建实例
  • 状态:pending(进行中),fufilled(已成功),rejected(已失败)
  • 参数:executor执行函数,自动执行
    • resolve 将状态由 pending 改为 fulfillled,触发then中成功的回调
    • reject 将状态由 pending 改为 rejected,触发then中失败的回调
  • then(onFulfilled 成功的函数, onRejected 失败的函数)
    • 如果不是函数,会被忽略
    • resolve 里的 value 作为 onFulfilled 函数的参数, reject同理
    • 必须返回一个promise,用于链式调用,上一次then的return值作为下一次then的执行参数
let promise = new Promise((resolve, reject) => { // executor
  // resolve('success'); 
  // reject('error');
  throw new Error('Exception: Error'); // 抛出错误走reject
});

promise.then((val) => {
    console.log(val, '成功的回调');
}, (reason) => {
    console.log(reason, '失败的回调');
})

要注意的是状态不受外界影响,一旦发生改变就会发生固化,不可逆

let promise = new Promise( (resolve, reject) => {
	resolve('ok');
	console.log(a);
})

promise
	.then(function(val) {
		console.log('success: ' + val); //success: ok
	})
	.catch(function(error) {
		console.log('reject: ' + error);
	})

状态固化之后,哪里都不会捕获异常。console.log(a)依然会执行,但是无法捕获

resolve和reject并不会终止函数运行

为什么要使用Promise?

要知道js是单线程的(多线程会造成DOM冲突),单线程阻塞页面的解决方案就是异步,异步是通过事件轮询的机制实现的,而事件轮询的核心就是回调函数。然而如果回调函数层层嵌套,造成回调地狱,就会导致代码难以维护和扩展。

const fs = require('fs');

fs.readFile('./name.txt', 'utf-8', (err, data) => {
	if (data) {
		fs.readFile(data, 'utf-8', (err, data) => {
			if (data) {
				fs.readFile(data, 'utf-8', (err, data) => {
					console.log(data);
				}
			}
		}
	}
})

使用Promise就可以使代码清晰很多

function readFile(path) {
	return new Promise( (resolve, reject) => {
		fs.readFile(path, 'utf-8', (err, data) => {
			if (data) {
				resolve(data);
			}
		}
	})
}
readFile('./name.txt')
	.then( data => readFile(data))
	.then( data => readFile(data))
	.then( data => console.log(data));

promisify

promisify方法可以将异步函数promise化,任何异步函数都适用

const fs = require('fs');

function promisify(func) {
	return function(...args) {
		return new Promise( (resolve, reject) => {
			func(...args, (err, data) => {
				if (data) {
					resolve(data);
				} else {
					reject(err);
				}
			}
		})
	}
}

let readFile = promisify(fs.readFile);
readFile('./name.txt', 'utf-8')
	.then(data => readFile(data, 'utf-8'))
	.then(data => readFile(data, 'utf-8'))
	.then(data => console.log(data));

事件轮询 - 宏任务与微任务

事件轮询的机制:执行JS代码的时候,遇到同步任务,直接压入主线程进行执行;遇到异步任务,让任务挂起,等到异步任务有返回值之后,推入任务队列中。等到主线程所有的同步代码执行完毕,将任务队列中的任务逐个推入主线程并执行,重复执行这一系列行为。

异步任务包含宏任务与微任务,微任务优先级高于宏任务

宏任务:宿主(浏览器/Node)提供的异步方法与任务(script,XHR回调,setTimeout,UI渲染,I/O,setImmediate)

微任务:语言标准(ECMA262)提供的API运行(Promise,Mutation Observer,process.nextTick)

setTimeout(function() {
	console.log('setTime')
}, 30);

let promise = new Promise(function(resolve, reject) {
	console.log(0);
	resolve(1);
});

promise.then( (val) => {
	console.log(val);
}, (reason) => {
	console.log(reason);
})
console.log(2); //0 2 1 setTime

过程解析:

  1. 看同步代码,Promise内部的函数执行是同步的(resolve才是promise的异步操作),所以0先打印,然后是2
  2. 微任务优先,所以先处理promise的回调,打印1
  3. 宏任务 setTimeout

Promise.all

参数:具有iterator接口的数据结构,如数组

保证在参数里的所有promise都成功的状态下,返回新的promise对象,并且把包含所有返回值的数组,作为成功回调的返回值

catch只会抓住第一个错误

let promise1 = new Promise((resolve, reject) => {
	setTimeout(function() {
		resolve('promise1: 1s');
	}, 1000);
})

let promise2 = new Promise((resolve, reject) => {
	setTimeout(function() {
		resolve('promise2: 2s');
	}, 2000);
})

let promise3 = new Promise((resolve, reject) => {
	setTimeout(function() {
		resolve('promise3: 3s');
	}, 3000);
})

const p = Promise.all([promise1, promise2, promise3]);

p.then(res => console.log(res))
 .catch(err => console.log('err: ' + err));
 
// 返回值:["promise1: 1s", "promise2: 2s", "promise3: 3s"]

手写Promise.all

Promise.all = function (iterator) {  
    let count = 0 //用于计数,当等于len时就resolve
    let len = iterator.length
    let res = [] //用于存放结果
    return new Promise((resolve, reject) => {
        for(let i in iterator){
            Promise.resolve(iterator[i])//先转化为Promise对象
                .then((data) => {
                        res[i] = data;
                        if(++count === len){
                                resolve(res)
                        }
                })
                .catch(e => {
                        reject(e)
                })
        }
    })
}

Promise.race

用法和all差不多,不一样的是,不管成功还是失败都只会返回第一个promise

Promise.race = function (iterators) {  
    return new Promise((resolve,reject) => {
        for (const p of iterators) {
            Promise.resolve(p)
                .then((res) => {
                    resolve(res)
                })
                .catch(e => {
                    reject(e)
                })
        }
    })
}

Promise.resolve

Promise.resolve / Promise.reject用法相同,都会返回一个新的Promise实例

可以使用resolve方法将普通对象转成promise对象,通过resolve方法部署值

// thenable:所有可以then的对象都是thenable对象
let thenable = {
	then: function(resolve, reject) {
		resolve(42);
	}
}
let p1 = Promise.resolve(thenable);
p1.then(val => console.log(val));

转空值或者字符串也可以

Promise.resolve('1').then((val) => console.log(val));
Promise.resolve().then(function(){
	console.log(2);
})

Promise重写

const PENDING = 'PENDING',
      FULFILLED = 'FULFILLED',
      REJECT = 'REJECT';

class MyPromise {
  constructor (executor) {
    this.status = PENDING;

    // 在pending阶段resolve和reject不明确
    this.value = undefined;
    this.reason = undefined;

    // 数组装载所有成功 / 失败的回调函数
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    // 每个promise的resolve/reject不同,不能放到原型上
    const resolve = (value) => {
      // 递归处理,直到value是个普通值
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
        return;
      }
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;

        // 等异步程序执行的时候,resolve执行,可以发布了
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    }

    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECT;
        this.reason = reason;

        // 发布
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }

    try {
      executor(resolve, reject); // executor立即执行
    } catch (e) {
      reject(e)
    }
  }

  // then会返回一个promise,但是then的return值会有很多种情况,如then直接返回一个promise,就不适用,所以需要一个x对返回值进行额外处理
  then (onFulfilled, onRejected) {
    // then 传空就把value传递下去即可,穿透
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value => value);
    onRejected = typeof onRejected === 'function' ? onRejected : (reason => throw reason);

    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === FULFILLED) {
        setTimeout(() => {
          try {
            // 不设置异步的话,resolvePromise拿不到promise2,需要promise2的new Promise先执行完,才可以拿到
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0)
      }

      if (this.status === REJECT) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
      
      // 异步处理,在pending的时候无法判断是成功的还是失败的回调函数,需要收集(订阅)
      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() => {
          // 这里不需要异步处理,因为pending状态本身就有异步处理了,resolve的时候才会执行
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        });

        this.onRejectedCallbacks.push(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        })
      }
    });

    return promise2;
  }

  // 用then模拟catch,catch实际上就是then的一个语法糖
  catch (errorCallback) {
    return this.then(null, errorCallback);
  }
  
  static resolve (value) {
    return new MyPromise((resolve, reject) => {
      resolve(value);
    })
  }

  static reject (error) {
    return new MyPromise((resolve, reject) => {
      reject(error);
    })
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject(new TypeError('promise名称不能和返回值相同'));
  }

  // 当then里面既有resolve,也有reject的时候,采用先执行的那个,需要一个flag判断是否调用过,避免重复调用resolve/reject
  let called = false;

  if ((typeof x === 'object' && x !== null) || (typeof x === 'function')) { // 可能是一个promise,需要再判断

    try {
      // 如果有then方法,基本上就是promise了
      let then = x.then; // 取then的时候可能有 throw error,有可能被defineProperty截止then(get),需要try catch

      if (typeof then === 'function') {
        // promise,改变this -> return的新Promise实例,执行then
        then.call(x, (y) => {
          if (called) return;
          called = true;
          // resolve(y); promise递归处理
          resolvePromise(promise2, y, resolve, reject);
        }, (r) => {
          if (called) return;
          called = true;
          reject(r);
        })
      } else {
        // 普通值
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
    
  } else {
    // 普通值
    resolve(x);
  }
}

总结:

  1. Promise是异步微任务,是异步问题同步化的解决方案,解决了异步多层回调嵌套的问题,使代码的可读性更高,更易维护

  2. Promise是ES6提供的一个构造函数,可以new一个实例出来,接受一个执行函数作为参数,会立即执行。

  3. Promise有三种状态:Pending进行中,Fulfilled已成功,rejected已失败,一旦状态改变了就不会变化了(状态固化),并且状态不受外界影响。

  4. Promise的执行函数有两个参数,resolve将Promise的状态由进行中变为成功,传递异步操作的结果,触发then内部成功的回调;reject将Promise状态变为失败,触发失败的回调。也可以通过catch捕捉失败,then和catch返回的也是一个Promise,用于链式调用。