# promise
- https://juejin.im/post/6844903632203153415
- https://juejin.im/post/6844904023988895757
- https://juejin.im/post/6844903591518404622
- https://juejin.im/post/6844903763178684430#heading-9
# 概述
- Promise 是异步编程的一种解决方案,其实是一个构造函数,自己身上有all、race、reject、resolve这几个方法,原型上有then、catch、finally等方法
- Promise 是一个对象,从它可以获取异步操作的结果。这个对象有2个特点,3种状态;
# 2个特点
- 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再变,任何时候都可以得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
# 3种状态
- pending(进行中)、fulfilled(已成功)和rejected(已失败)
# 优点
- 有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
# 缺点
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部
- 当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)
# 应用
# 1.手写promise
- https://zhuanlan.zhihu.com/p/21834559
- 简版
// 手写promise class Promise1{ constructor(fn){ // 初始化state为等待态 this.state = 'pending' // 初始化成功的参数 this.value = undefined // 初始化失败的参数 this.reason = undefined // resolve调用,state变为成功态,并将要传递的参数,挂到this.value上,方便回调onFulfilled调用 let resolve = (value) => { if(this.state === 'pending'){ this.state = 'resolved' this.value = value } } // reject调用,state变为失败态,并将要传递的参数,挂到this.reason上,方便回调onRejected调用 let reject = (reason) => { if(this.state === 'pending'){ this.state = 'rejected' this.reason = reason } } // 自动执行函数 try{ fn(resolve, reject) }catch(err){ reject(err) } } then(onFulfilled, onRejected){ if(this.state === 'resolved'){ onFulfilled(this.value) } if(this.state === 'rejected'){ onRejected(this.reason) } } } new Promise1(function(resolve, reject){ resolve(2) console.log(0) }).then(console.log) // 0 2
- 复杂版
class Promise {
constructor(executor){
this.state = 'pending'
this.value = undefined
this.reason = undefined
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
let resolve = value => {
if(this.state === 'pending'){
this.state = 'fulfilled'
this.value = value
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = value => {
if(this.state === 'pending'){
this.state = 'rejected'
this.reason = value
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try {
executor(resolve,reject)
}catch(err){
reject(err)
}
}
then(onFulfilled, onRejected){
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
onRejected = typeof onRejected === 'function' ? onRejected : reason =>{ throw reason }
let promise2 = new Promise((resolve, reject)=>{
if(this.state === 'fulfilled'){
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(err){
reject(err)
}
},0)
}
if(this.state === 'rejected'){
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(err){
reject(err)
}
},0)
}
if(this.state === 'pending'){
this.onFulfilledCallbacks.push(()=>{
setTimeout(() => {
try {
let x = onFulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(err){
reject(err)
}
},0)
})
this.onRejectedCallbacks.push(()=>{
setTimeout(() => {
try {
let x = onRejected(this.reason)
resolvePromise(promise2,x,resolve,reject)
}catch(err){
reject(err)
}
},0)
})
}
})
return promise2;
}
catch(fn){
return this.then(null, fn)
}
}
// 实现。then的链式调用
function resolvePromise(promise2,x,resolve,reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'))
}
let called;
if(x!==null && (typeof x === 'object' || typeof x === 'function')){
try {
let then = x.then
if(typeof then === 'function'){
then.call(x, y => {
if(called) return
called = true
resolvePromise(promise2, y, resolve, reject)
}, err => {
if(called) return
called = true
reject(err)
})
}else{
resolve(x)
}
}catch(e){
if(called) return
called = true
reject(e)
}
}else{
resolve(x)
}
}
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject) => {
resolve(val)
})
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject) => {
reject(val)
})
}
//race方法
Promise.race = function(promises){
return new Promise((resolve, reject)=>{
promises.forEach(promise => {
promise.then(resolve,reject)
})
})
}
// all方法
Promise.all = function(promises){
let arr = [], index=0;
let len = promises.length
return new Promise((resolve,reject) => {
for(let i=0; i<len; i++){
promises[i].then(data => {
index++
arr[i] = data;
if(index === len){
return resolve(arr)
}
}, reject)
}
})
}
# 2.实现一个延时函数
function sleep(time){
return new Promise(function(resolve,reject){
setTimeout(() => {
resolve(true)
}, time*1000)
})
}
# 3.实现一个retry函数
- promise.retry 的作用是执行一个函数,如果不成功最多可以尝试 times 次。传参需要三个变量,所要执行的函数,尝试的次数以及延迟的时间。
function retry(fn, times, delay){
return new Promise((resolve, reject)=>{
let errors = []
function attempt(){
if(times===0){
reject(errors)
}else{
fn().then(v=>resolve(v))
.catch(e=>{
errors.push(e)
times--;
setTimeout(() => attempt(),delay)
})
}
}
attempt()
})
}
# 4.将node的回调中同步的callback包装为promise形式
nodeGet(param, function (err, data) { })
// 转化成promise形式
function nodeGetAysnc(param) {
return new Promise((resolve, reject) => {
nodeGet(param, function (err, data) {
if (err !== null) return reject(err)
resolve(data)
})
})}
// 按照上面的思路,即可写出通用版的形式。
function promisify(fn,context){
return (...args) => {
return new Promise((resolve,reject) => {
fn.apply(context,[...args,(err,res) => {
return err ? reject(err) : resolve(res)
}])
})
}
}
function promisify(fn, ctx){
return (...args) => {
return new Promise(function(resolve,reject){
fn.apply(ctx, [...args, (err,res) => {
return err ? reject(err) : resolve(res)
}])
})
}
}
# 5.异步加载图片
function loadImageAsync(url){
return new Promise(function(resolve,reject){
const image = new Image()
image.onload = function(){
resolve(image)
}
image.onerror = function(){
reject(new Error('could not load image at'+url))
}
image.src = url
})
}
# 6.用Promise对象实现的 Ajax 操作
const getJSON = function(url){
return new Promise(function(resolve,reject){
const xhr = new XMLHttpRequest();
xhr.open('GET', url)
xhr.setRequestHeader('Accept','application/json')
xhr.responseType = 'json';
xhr.onreadystatechange = function(){
if(xhr.readyState !== 4){
return;
}
if(xhr.status === 200){
resolve(xhr.responseText)
}else{
reject(new Error(this.statusText))
}
}
xhr.send()
})
}
getJSON('./posts.json').then()
# 7.promise异步流程控制
- https://juejin.im/post/59cdb6526fb9a00a4e67c7fb#heading-8
function loadImg(url){
return new Promise(function(resolve,reject){
const img = new Image()
img.onload = function(){
resolve(img)
}
img.onerror = reject
img.src = url
})
}
let promise = Promise.resolve()
for(let i=0,len=urls.length; i<len;i++){
promise = promise.then(()=> loadImg(urls[i]))
.then(()=> addToHtml)
}
urls.reduce((promise,url) =>{
return promise.then(() => loadImg(url))
.then(()=> addToHtml)
}, Promise.resolve()).then(() => {
document.querySelector('.loading').style.display = 'none'
})
function syncLoad(index){
if(index>=urls.length) return Promise.resolve()
loadImg(urls[index])
.then(img => {
addToHtml(img)
return syncLoad(index+1)
})
}
syncLoad(0)
.then(()=>{
document.querySelector('.loading').style.display = 'none'
})
function syncLoad(fn, arr, handler){
const errors = []
return arr.reduce((promise, url)=>{
return promise.then(() => fn(url))
.then((img)=> handler(img))
.catch((err)=>{
console.log(err)
errors.push(url)
})
}, Promise.resolve()).then(() => {
document.querySelector('.loading').style.display = 'none'
}).catch(console.log)
}
syncLoad(imgLoad, urls, addToHtml)
.then(()=>{
document.querySelector('.loading').style.display = 'none'
})
.catch(console.log)
const promises = urls.map(loadImg)
Promise.all(promises)
.then(imgs =>{
imgs.forEach(addToHtml)
document.querySelector('.loading').style.display = 'none'
})
.catch(err => {
console.log(err)
})
const promises = urls.map(loadImg)
promises.reduce((task, imgPromise)=>{
task.then(() => imgPromise).then(()=>addToHtml)
}, Promise.resolve())
# 8.控制最大并发数
- 微信小程序最一开始对并发数限制为5个,后来升级到10个,如果超过10个会被舍弃。后来微信小程序升级为不限制并发请求,但超过10个会排队机制。也就是当同时调用的请求超过 10 个时,小程序会先发起 10 个并发请求,超过 10 个的部分按调用顺序进行排队,当前一个请求完成时,再发送队列中的下一个请求。
function limitLoad(urls, handler, limit){
const newUrls = [].concat(urls)
let count = 0
const promises = []
function load(){
if(newUrls.length<=0 || count >limit) return
count+=1
return handler(newUrls.shift())
.catch(err=>{
console.log(err)
})
.then(() => {
count-=1
})
.then(() =>{
load()
})
}
for(let i=0,len=newUrls.length; i<limit && i<len;i++){
promises.push(load())
}
return Promise.all(promises)
}
function concurrentPoll(){
this.tasks = [];
this.max = 10;
setTimeout(() => {
this.run()
},0)
}
concurrentPoll.prototype.addTask = function(task){
this.tasks.push(task)
}
concurrentPoll.prototype.run = function(){
if(this.tasks.length == 0){
return
}
var min = Math.min(this.tasks.length, max);
for(var i = 0; i < min; i++){
this.max--;
var task = this.tasks.shift();
task().then((res) => {
console.log(res)
}).catch((err) => {
console.log(err)
}).finally(() => {
this.max++;
this.run();
})
}
}
#
- 前端很常见是下面一个场景,我们需要实现一个用户修改头像的功能。首先我们需要将一张图片压缩并提交给后端,后端返回该图片保存的 url,前端拿保存的 url 和用户 id 提交给服务器来修改用户头像。
- 异步一:加载图片
- 异步二:压缩图片
- 异步三:上传图片
- 异步四:提交保存
- 大概代码实现
Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3])=>{})