Promise原理详解及实现方式

在异步编程中,许多操作都会放在回调函数(callback)中,有时候需要拿到上一个异步操作的返回值再做第二次请求

比如:

asyncOperation(data => {
  // 处理 `data`
  anotherAsync(data2 => {
      // 处理 `data2`
      yetAnotherAsync(() => {
          // 完成
      })
  })
})

上面代码中,每增加一个异步请求,就会多添加一层回调函数的嵌套,过多的回调也就让我们陷入“回调地狱”,让代码变得不易阅读与维护

引入 Promises 之后的代码

promiseSomething()
  .then(data => {
    // 处理 `data`
    return anotherAsync()
  })
  .then(data2 =>{
    // 处理 `data2`
    return yetAnotherAsync()
  })
  .then(() => {
    // 完成
  })

Promises 将嵌套的 callback,改造成一系列的.then的连缀调用,去除了层层缩进的糟糕代码风格

注意:Promise 本身不是异步,往往内部都是封装一个异步任务


Promise 状态

  1. pending(进行中):等待状态,比如正在进行网络请求或者定时器没有到时间
  2. fulfilled (已成功):满足状态,当主动回调了 resolve 时,就处于该状态,并且回调.then()
  3. rejected(已失败):拒绝状态,当主动回调了 reject 时,就处于该状态,并且回调.catch()

Promise对象的状态改变,只有两种可能:从pending变为 fulfilled 和从pending变为rejected,一旦确定就不会再改变


创建 Promise

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由 JavaScript 引擎提供,不用自己部署

const promise = new Promise((resolve, reject) => {
  if (/* 异步操作成功 */) {
    //如果 true ,把容器的状态改为 resolve
    resolve(value)
  } else {
    //如果 false ,把容器的状态改为 reject
    reject(error)
  }
})

resolve函数的作用是:将Promise对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去

reject函数反之


Promise 原理分析

promise 内部有三个状态,分别是pending,fulfilled和rejected

pending是对象创建后的初始状态,当调用resolve函数时变为fulfilled(成功)状态(可调用.then方法进行成功处理),当调用reject函数时变为rejected (失败)状态(可调用.catch方法进行失败处理)

promise
  .then(value => {
    // 成功的回调,接收的就是容器中的 resolve 函数
    // 容器中 resolve 函数传的是什么,这里接收就是什么
    console.log(value)
  })
  .catch(error => {
    // 失败的回调,接收的就是容器中的 reject 函数
    console.log(error)
  })

链式写法

//封装 Promise API
function fzPromise(isFlag,data,err) {
  return new Promise((resolve, reject) => {
    if (isFlag) {
      resolve(data)
    } else {
      reject(err)
    }
  });
}
const promiseOne = new fzPromise(true,1,'操作失败');
const promiseTwo = new fzPromise(true, 2, '操作失败');
const promiseThree = new fzPromise(false, 3, '操作失败');

promiseOne
  .then((data) => {
    console.log(data);
    //当 promiseOne 读取成功,当前函数 return 的结果就能在后面的 then 中的回调函数接收到
    return promiseTwo
  },(err) => {
    console.log(err);
  })
  //这里是 promiseTwo 的 resolve
  .then((data) => {
    console.log(data);
    return promiseThree
  },(err) => {
    console.log(err);
  })
  //这里是 promiseThree 的 resolve
  .then((data) => {
    console.log(data);
  },(err) => {
    //因为 promiseThree 是 false ,所以结果是 reject 传的 '操作失败'
    console.log(err);
  })
    
//1
//2
//操作失败

Promise 封装 ajax

let ajax = function(url, params, type = 'POST') {
  return new Promise((resolve, reject) => {
    $.ajax({
      type,
      url,
      data: params,
      dataType: 'json',
      success(res) {
        resolve(res)
      },
      error(res) {
        reject('响应错误')
      }
    })
  })
} 

// 使用示例
ajax('url',{channelIds: 1}).then(res => {
  console.log(res)
})

Promise 其他静态方法

1.Promise.resolve()

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))
let thenable = {
  then(resolve, reject) {
    resolve(42);
  }
}

let p1 = Promise.resolve(thenable);
p1.then(function(value) {
  console.log(value);  // 42
})

2.Promise.reject()

const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))

p.then(null, function (s) {
  console.log(s)
});
// 出错了

3.Promise.all()

用于将多个 Promise 实例,包装成一个新的 Promise 实例, 接受一个数组作为参数, 可以不是数组,但必须具有 Iterator 接口

const p = Promise.all([p1, p2, p3])

p的状态由p1p2p3决定,分成两种情况

  1. 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数
  2. 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数
const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

上面代码中,p1会resolved,p2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数

4.Promise.race()

同样是将多个 Promise 实例,包装成一个新的 Promise 实例, 只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数

const p = Promise.race([
  fetch('/resource-that-may-take-a-while'),
  new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('request timeout')), 5000)
  })
]);

p
.then(console.log)
.catch(console.error);

上面代码中,如果 5 秒之内fetch方法无法返回结果,变量p的状态就会变为rejected,从而触发catch方法指定的回调函数

5.Promise.allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例。只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束

手写Promise

function MyPromise(fn) {
  const that = this
  this.status = 'padding' // 定义初始状态
  this.value = '' // 定义初始值

  function resolve(value) {
    if (that.status === 'padding') {
      that.status = 'resolve'
      that.value = value
    }
  }

  function reject(value) {
    if (that.status === 'padding') {
      that.status = 'reject'
      that.value = value
    }
  }

  fn(resolve, reject)
}

// 定义 then 方法
MyPromise.prototype.then = function(onResolve, onReject) {
  const that = this
  if (this.status === 'resolve') {
    onResolve(this.value)
  }
  if (this.status === 'reject') {
    onReject(this.value)
  }
}

new MyPromise((resolve, reject) => {
  // resolve('1')
  reject('2')
})
.then(res => {
  console.log(res)
}, err => {
  console.log(err)
})

你可能感兴趣的:(前端常见面试题,#,ES6篇,es6,javascript)