关于javascript:Promise详解

6次阅读

共计 7994 个字符,预计需要花费 20 分钟才能阅读完成。

前言:


日常开发中,异步操作简直每天都能见到。传统的意不解决方案是通过回调函数,随着程序逻辑越来越简单,回调函数的形式变得越来越繁琐,很容易呈现回调天堂,于是一种更正当更弱小的代替计划呈现 –Promise,接下来就深刻学习 Promise 是如何解决异步操作的。

一. 根底

定义: Promise 对象用于示意一个异步操作的最终实现 (或失败), 及其后果值. <!– more –>

const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('foo');
  }, 300);
});

promise1.then((value) => {console.log(value);
  // expected output: "foo"
});

console.log(promise1);
// expected output: [object Promise]

运行下面这段代码,先是打印 [object Promise],300ms 后打印 foo

语法

new Promise(function(resolve, reject) {...} /* executor */  )

参数 executor executor 是带有 resolve 和 reject 两个参数的函数。Promise 构造函数执行时立刻调用 executor 函数,resolve 和 reject 两个函数作为参数传递给 executor(executor 函数在 Promise 构造函数返回 promise 实例对象前被调用)。resolve 和 reject 函数被调用时,别离将 promise 的状态改为 fulfilled(实现)或 rejected(失败)。executor 外部通常会执行一些异步操作,一旦异步操作执行结束 (可能胜利 / 失败),要么调用 resolve 函数来将 promise 状态改成 fulfilled,要么调用 reject 函数将 promise 的状态改为 rejected。如果在 executor 函数中抛出一个谬误,那么该 promise 状态为 rejected。executor 函数的返回值被疏忽。

这段形容合成下就是:

  1. 实例化 Promise 对象时须要传入一个 executor 函数,所有业务代码都须要写在这个函数中;
  2. executor 函数在构造函数执行时就会调用,此时实例化对象还并没有被创立,resolve 和 reject 两个函数作为参数传递给 executor,resolve 和 reject 函数被调用时,别离将 promise 的状态改为 fulfilled(实现)或 rejected(失败)。一旦状态扭转,就不会再变,任何时候都能够失去这个后果。
  3. 如果 executor 中代码抛出了谬误,promise 状态为 rejected;
  4. executor 函数的返回值被疏忽。

状态 一个 Promise 有以下几种状态:

  • pending: 初始状态,既不是胜利,也不是失败状态。
  • fulfilled: 意味着操作胜利实现。
  • rejected: 意味着操作失败。

pending 状态的 Promise 对象可能会变为 fulfilled 状态并传递一个值给相应的状态解决办法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种状况呈现时,Promise 对象的 then 办法绑定的解决办法(handlers)就会被调用(then 办法蕴含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当 Promise 状态为 fulfilled 时,调用 then 的 onfulfilled 办法,当 Promise 状态为 rejected 时,调用 then 的 onrejected 办法,所以在异步操作的实现和绑定解决办法之间不存在竞争)。

const promise1 = new Promise((resolve, reject) => {setTimeout(() => {resolve('fulfilled');
  }, 1000);
});

const promise2 = new Promise((resolve, reject) => {setTimeout(() => {reject('rejected');
  }, 3000);
});

promise1.then((fulfilled) => {console.log(fulfilled);
},(rejected)=>{console.log(rejected);
});
promise2.then((fulfilled) => {console.log(fulfilled);
},(rejected)=>{console.log(rejected);
});

运行下面这段代码,1s 后打印 fulfilled,3s 后打印 rejected rejected 状态的 Promise 也能够通过.catch 进行捕捉,因为 Promise.prototype.then 和 Promise.prototype.catch 办法返回 promise 对象,所以它们能够被链式调用。所以上述代码能够改为:

promise1.then((fulfilled) => {console.log(fulfilled);
}).catch((rejected)=>{console.log(rejected);
});
promise2.then((fulfilled) => {console.log(fulfilled);
}).catch((rejected)=>{console.log(rejected);
});

二. 深刻了解

1.Promise 是用来治理异步编程的 ,它自身不是异步的,new Promise 的时候会立刻把 executor 函数执行,只不过咱们个别会在 executor 函数中解决一个异步操作。例如上面一段代码:

let firstPromise = new Promise(()=>{setTimeout(()=>{console.log(1)
    },1000)
    console.log(2)
  })
console.log(3) // 2 3 1

2.Promise 采纳了回调函数提早绑定技术 ,在执行 resolve 函数的时候,回调函数还没有绑定,那么只能推延回调函数的执行。这具体是啥意思呢?咱们先来看上面的例子:

let p1 = new Promise((resolve,reject)=>{console.log(1);
  resolve('浪里行舟')
  console.log(2)
})
// then: 设置胜利或者失败后处理的办法
p1.then(result=>{
 //p1 提早绑定回调函数
  console.log('胜利'+result)
},reason=>{console.log('失败'+reason)
})
console.log(3)
// 1
// 2
// 3
// 胜利 浪里行舟

new Promise 的时候先执行 executor 函数,打印出 1、2,Promise 在执行 resolve 时,触发微工作,还是持续往下执行同步工作,执行 p1.then 时,存储起来两个函数(此时这两个函数还没有执行), 而后打印出 3,此时同步工作执行实现,最初执行刚刚那个微工作,从而执行.then 中胜利的办法。

3. 错误处理 ,多个 Promise 链式操作的谬误捕捉能够通过一个 catch 解决;例如上面一段代码:

let executor = function(resolve,reject){let random = Math.random()
    if(random>0.5){resolve()
    }else{reject()
    }
}
let p1 = new Promise(executor)
p1.then(resualt=>{console.log(1)
    return new Promise(executor)
}).then(resualt=>{console.log(2)
    return new Promise(executor)
}).then(resualt=>{console.log(3)
    return new Promise(executor)
}).catch((error) => {console.log('error', error)
})

这段代码有四个 Promise 对象,无论哪个对象外面抛出异样,都能够通过最初一个.catch 来捕捉异样,通过这种形式能够将所有 Promise 对象的谬误合并到一个函数来解决,这样就解决了每个工作都须要独自解决异样的问题。

4. 罕用办法

  • Promise.resolve()  Promise.resolve(value) 办法返回一个以给定值解析后的 Promise 对象。Promise.resolve() 等价于上面的写法:
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.resolve 办法的参数分成四种状况。

(1)参数是一个 Promise 实例

const p1 = new Promise(function (resolve, reject) {setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {setTimeout(() => resolve(p1), 1000)
})
p2
  .then(result => console.log(result))
  .catch(error => console.log(error))
// Error: fail

下面代码中,p1 是一个 Promise,3 秒之后变为 rejected。p2 的状态在 1 秒之后扭转,resolve 办法返回的是 p1。因为 p2 返回的是另一个 Promise,导致 p2 本人的状态有效了,由 p1 的状态决定 p2 的状态。所以,前面的 then 语句都变成针对后者(p1)。又过了 2 秒,p1 变为 rejected,导致触发 catch 办法指定的回调函数。

(2)参数不是具备 then 办法的对象,或基本就不是对象

Promise.resolve("Success").then(function(value) {
 // Promise.resolve 办法的参数,会同时传给回调函数。console.log(value); // "Success"
}, function(value) {// 不会被调用});

(3)不带有任何参数 Promise.resolve() 办法容许调用时不带参数,间接返回一个 resolved 状态的 Promise 对象。如果心愿失去一个 Promise 对象,比拟不便的办法就是间接调用 Promise.resolve() 办法。

Promise.resolve().then(function () {console.log('two');
});
console.log('one');
// one two

(4)参数是一个 thenable 对象 thenable 对象指的是具备 then 办法的对象,Promise.resolve 办法会将这个对象转为 Promise 对象,而后就立刻执行 thenable 对象的 then 办法。

let thenable = {then: function(resolve, reject) {resolve(42);
  }
};
let p1 = Promise.resolve(thenable);
p1.then(function(value) {console.log(value);  // 42
});
  • Promise.reject() Promise.reject() 办法返回一个带有回绝起因的 Promise 对象。
new Promise((resolve,reject) => {reject(new Error("出错了"));
});
// 等价于
 Promise.reject(new Error("出错了"));  

// 应用办法
Promise.reject(new Error("BOOM!")).catch(error => {console.error(error);
});

值得注意的是,调用 resolve 或 reject 当前,Promise 的使命就实现了,后继操作应该放到 then 办法外面,而不应该间接写在 resolve 或 reject 的前面。所以,最好在它们后面加上 return 语句,这样就不会有意外。

new Promise((resolve, reject) => {return reject(1);
  // 前面的语句不会执行
  console.log(2);
})
  • Promise.all()
var p1 = Promise.resolve(1)
var p2 = Promise.resolve({a:2})
var p3 = new Promise(function(resolve,reject){setTimeout(function(){resolve(3)
    },3000)
})
Promise.all([p1,p2,p3]).then(result=>{
    // 返回的后果是依照 Array 中编写实例的程序来
    console.log(result)
})

Promise.all 生成并返回一个新的 Promise 对象,所以它能够应用 Promise 实例的所有办法。参数传递 promise 数组中所有的 Promise 对象都变为 resolve 的时候,该办法才会返回,新创建的 Promise 则会应用这些 promise 的值。 如果参数中的任何一个 promise 为 reject 的话,则整个 Promise.all 调用会立刻终止,并返回一个 reject 的新的 Promise 对象。

  • Promise.allSettled()

有时候,咱们不关怀异步操作的后果,只关怀这些操作有没有完结。这时,ES2020 引入 Promise.allSettled() 办法就很有用。如果没有这个办法,想要确保所有操作都完结,就很麻烦。Promise.all() 办法无奈做到这一点。

如果有这样的场景:一个页面有三个区域,别离对应三个独立的接口数据,应用 Promise.all 来并发申请三个接口,如果其中任意一个接口出现异常,状态是 reject, 这会导致页面中该三个区域数据全都无奈进去,显然这种情况咱们是无奈承受,Promise.allSettled 的呈现就能够解决这个痛点:

Promise.allSettled([Promise.reject({ code: 500, msg: '服务异样'}),
  Promise.resolve({code: 200, list: [] }),
  Promise.resolve({code: 200, list: [] })
]).then(res => {console.log(res)
  /*
    0: {status: "rejected", reason: {…}}
    1: {status: "fulfilled", value: {…}}
    2: {status: "fulfilled", value: {…}}
  */
  // 过滤掉 rejected 状态,尽可能多的保障页面区域数据渲染
  RenderContent(
    res.filter(el => {return el.status !== 'rejected'})
  )
})

Promise.allSettled 跟 Promise.all 相似, 其参数承受一个 Promise 的数组, 返回一个新的 Promise, 惟一的不同在于, 它不会进行短路, 也就是说当 Promise 全副解决实现后, 咱们能够拿到每个 Promise 的状态, 而不论是否解决胜利。

  • Promise.race()

Promise.all() 办法的成果是 ” 谁跑的慢,以谁为准执行回调 ”,那么绝对的就有另一个办法 ” 谁跑的快,以谁为准执行回调 ”,这就是 Promise.race() 办法,这个词原本就是赛跑的意思。race 的用法与 all 一样,接管一个 promise 对象数组为参数。

Promise.all 在接管到的所有的对象 promise 都变为 FulFilled 或者 Rejected 状态之后才会持续进行前面的解决,与之绝对的是 Promise.race 只有有一个 promise 对象进入 FulFilled 或者 Rejected 状态的话,就会持续进行前面的解决。

// `delay` 毫秒后执行 resolve
function timerPromisefy(delay) {
    return new Promise(resolve => {setTimeout(() => {resolve(delay);
        }, delay);
    });
}
// 任何一个 promise 变为 resolve 或 reject 的话程序就进行运行
Promise.race([timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64)
]).then(function (value) {console.log(value);    // => 1
});

下面的代码创立了 3 个 promise 对象,这些 promise 对象会别离在 1ms、32ms 和 64ms 后变为确定状态,即 FulFilled,并且在第一个变为确定状态的 1ms 后,.then 注册的回调函数就会被调用。

  • Promise.prototype.finally()

ES9 新增 finally() 办法返回一个 Promise。在 promise 完结时,无论后果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否胜利实现后都须要执行的代码提供了一种形式。这防止了同样的语句须要在 then() 和 catch() 中各写一次的状况。 比方咱们发送申请之前会呈现一个 loading,当咱们申请发送实现之后,不论申请有没有出错,咱们都心愿关掉这个 loading。

this.loading = true
request()
  .then((res) => {// do something})
  .catch(() => {// log err})
  .finally(() => {this.loading = false})

finally 办法的回调函数不承受任何参数,这表明,finally 办法外面的操作,应该是与状态无关的,不依赖于 Promise 的执行后果。

三. 理论利用

假如有这样一个需要:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?三个亮灯函数曾经存在:

function red() {console.log('red');
}
function green() {console.log('green');
}
function yellow() {console.log('yellow');
}

这道题简单的中央在于须要“交替反复”亮灯,而不是亮完一遍就完结,咱们能够通过递归来实现:

// 用 promise 实现
let task = (timer, light) => {return new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()
      }
      if (light === 'green') {green()
      }
      if (light === 'yellow') {yellow()
      }
      resolve()}, timer);
  })
}
let step = () => {task(3000, 'red')
    .then(() => task(1000, 'green'))
    .then(() => task(2000, 'yellow'))
    .then(step)
}
step()

同样也能够通过 async/await 的实现:

//  async/await 实现
let step = async () => {await task(3000, 'red')
  await task(1000, 'green')
  await task(2000, 'yellow')
  step()}
step()

参考资料 你真的懂 Promise 吗

正文完
 0