JavaScript 之 Promise

开始之前

JavaScript是单线程语言,所以都是同步执行的,要实现异步就得通过回调函数的方式,但是过多的回调会导致回调地狱,代码既不美观,也不易维护,所以就有了promise;Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。Promise 是一个拥有 then 方法的对象或函数。

Promise

问题探讨

下面通过多个示例来感受一下不使用 promise 时,处理相应问题的不易,及生成了不便阅读的代码。

定时嵌套

假设我们想要实现网页上一个盒子先向右边移动,移动到一定距离后逐渐缩小,缩小到一定大小后停止缩小。下面我们用两个定时器去实现,一个定时器控制移动,当该定时器结束后启动另一个定时器控制缩小。

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
    <style type="text/css">
        #box{
            height: 100px;
            width: 100px;
            background-color: #3498db;
            position: absolute;
        }
    </style>
</head>
<body>
    <div id="box"></div>
    <script type="text/javascript">
        function interval(callback,delay=100){
            let id = setInterval(()=>callback(id),delay)
        }
        const box = document.getElementById('box');
        interval((timeId)=>{
            const left = Number.parseInt(window.getComputedStyle(box).left);
            box.style.left = left + 10 + 'px';
            if(left > 200){
                clearInterval(timeId);
                interval((timeId)=>{
                    const width = Number.parseInt(window.getComputedStyle(box).width);
                    const height = Number.parseInt(window.getComputedStyle(box).height);
                    box.style.width = width - 5 + 'px';
                    box.style.height = height - 5 + 'px';
                    if(width <= 50){
                        clearInterval(timeId)
                    }
                })
            }
        });
    </script>
</body>
</html>

图片加载

图片加载完成后设置一个图片边框

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        function loadImg(file,resolve,reject){
            const img = new Image();
            img.src = file;
            img.onload = () => {
                resolve(img);
            };
            img.onerror = () => {
                reject(new Error('load fail'))
            }
            document.body.appendChild(img);
        }
        loadImg('https://static.collectui.com/shots/4418237/health-landing-page-large',
            image => {
                console.log('图片加载成功,开始修改边框');
                image.style.border = "5px solid #3498db";
            },
            error => {
                console.log(error);
            }
        )
    </script>
</body>
</html>

疫情解除,员工复工通告

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        function notice(msg,then){
            then(msg);
        }
        function work(){
            notice('全国上下正在积极应对疫情变化', msg=>{
                console.log(msg);
                notice('疫情已经结束,请各级政府通知企业复工', msg => {
                    console.log(`收到党中央,国务院消息:${msg}`);
                    setTimeout(()=>{
                        notice('请各企业有序复工', msg => {
                            console.log(`收到政府消息:${msg}`);
                            notice('请员工在规定时间内到到岗上班,但仍需注意防护', msg => {
                                console.log(`收到企业消息:${msg}`);
                                setTimeout(()=>{
                                    notice('员工已到岗',msg => {
                                        console.log(`收到员工消息:${msg},通知结束`);
                                    })
                                },1000)
                            })
                        })
                    },1000)
                })
            })
        }
        work();
    </script>
</body>
</html>

上面的实例可以看出在没有使用Promise的时候,一些相互关联的异步操作就会一层层的嵌套,形成了回调地狱,而且对于阅读和维护代码带来了极大的不便。

Promise 基本语法

Promise 初体验

在开始讲解基本语法之前先改一下疫情复工通知的例子,感受一下promise带来的便利。

<!DOCTYPE html>
<html>
<head>
    <title>JavaScript</title>
</head>
<body>
    <script type="text/javascript">
        let country = new Promise((resolve,reject)=>{
            console.log('全国上下正在积极应对疫情变化');
            resolve('疫情已经结束,请各级政府通知企业复工');
        })
        let goverment = country.then(msg=>{
            console.log(`收到党中央,国务院消息:${msg}`);
            return {
                then(resolve){
                    setTimeout(()=>{
                        resolve('请各企业有序复工');
                    },1000);
                }
            }
        })
        let enterment = goverment.then(msg=>{
            console.log(`收到政府消息:${msg}`);
            return{
                then(resolve){
                    setTimeout(()=>{
                        resolve('请员工在规定时间内到岗上班,但仍需注意防护')
                    },1000);
                }
            }
        })
        let employee = enterment.then(msg=>{
            console.log(`收到企业消息:${msg}`);
            return{
                then(resolve){
                    setTimeout(()=>{
                        resolve('员工已到岗');
                    },1000)
                }
            }
        })
        employee.then(msg=>{
            console.log(`收到员工消息:${msg},通知结束`);
        })
    </script>
</body>
</html>

使用promise后,保证实现了原有功能,写法上各级专注负责自己所管辖的内容,避免了代码的相互嵌套,使代码变得更加容易阅读,维护起来也更加方便。

理解Promise

promise可以按照字面意思理解——承诺。那这次疫情来讲。国家政府通知全民可以进行正常的工作生活就是对人民的一种承诺。如果疫情控制住了,结束了就是成功,我们就可以不用在家办公或者隔离了。如果疫情没有彻底控制住,就是拒绝,我们仍好继续现在的生活工作状态。

  • 一个promise 必须有一个then的方法用于处理状态和改变。
    状态说明:
    Promise包含pending fulfilled reject三种状态
  • pending指初始等待状态,初始化promise的状态
  • resolve指已经解决,将promise状态设置为fulfilled
  • reject指拒绝处理,将promise状态设置为rejected
  • promise是生产者,通过resolvereject函数告知结果
  • promise非常适合需要一定执行时间的异步任务(一般网络请求都是异步任务)
  • 状态量一旦改变就不可改改变
    promise是一种队列状态,就像多米多骨牌,状态由上个传递到下一个,然后一个个往下传,当然其中任何一个promise也是可以改变状态的。
    图片2

    promise在没有使用resolvereject更改状态时,状态为pending
console.log(
    new Promise((resolve,reject)=>{});
) // Promise(<pending>)

promise使用ressolvereject改变状态后

console.log(
  new Promise((resolve, reject) => {
    resolve("fulfilled");
  })
); //Promise {<resolved>: "fulfilled"}

console.log(
  new Promise((resolve, reject) => {
    reject("rejected");
  })
); //Promise {<rejected>: "rejected"}

promise创建任务时会立即执行同步任务,then会放在异步微任务中执行,需要等待同步任务执行后才执行。

let promise = new Promise((resolve,reject)=>{
  resolve('fulfilled');
  console.log('第一次打印');
});
promise.then(msg=>{
  console.log(msg);
});
console.log('第二次打印')

图片3

上面的结果可以看出,promise状态改变后产生的微任务会在所有宏任务执行后在执行。

promise操作都是在其他代码后执行,下面会先输出第一次输出,之后才会弹出success

  • promisethen catch finally的方法都是异步任务
  • 程序需要将主任务执行完才会执行一步队列任务
let promise  = new Promise((resolve,reject)=>{resolve('success')})
promise.then(alert);
alert('第一次输出');
promise.then(()=>{
  alert('success之后弹出')
})

下面这个例子将在3秒后将promise状态设置为fulfilled,然后执行then方法

new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('fulfilled');
  },3000)
}).then(
  (msg)=>{
    console.log(msg);
  },
  (error)=>{
    console.log(error);
  }
)

状态改变了就不能再修改了,下面先通过resolve改变为成功状态,表示promise状态已经完成,就不能用使用reject更改状态了

new Promise((resolve, reject) => {
  resolve("操作成功");
  reject(new Error("请求失败"));
}).then(
  msg => {
    console.log(msg);
  },
  error => {
    console.log(error);
  }
);

下面的例子中p2返回了p1的状态已经无意义了,后面then时对p1状态的处理。即如果resolve参数是一个promise将会改变他的状态。

let p1 = new Promise((resolve,reject)=>{
  resolve('fulfilled');
  // reject('reject');
});
let p2 = new Promise((resolve,reject)=>{
    resolve(p1);
}).then(
    value=>{
        console.log(value);
    },
    reason=>{
        console.log(reason);
    }
);

promise作为参数传递时,需要等待promise执行完才可以继承,下面的p2需要等待p1执行完成。

  • 因为p2resolve 返回了 p1的promise,所以此时p2then方法已经时p1的状态了
  • 正因为以上原因then的第一个函数输出了p1resolve的参数
let p1 = new Promise((resolve,reject)=>{
  setTimeout(()=>{
    resolve('操作成功');
  },2000)
})
let p2 = new Promise((resolve,reject)=>{
  resolve(p1);
})
p2.then(msg=>{
  console.log(msg);
}).catch(error=>{
  console.log(error)
})

then的使用

一个promise需要提供一个then方法来访问promise的结果,then相当于定义当promise状态发生改变的处理,即promise处理异步操作,then用来处理结果。

promise就像疫情中的真政府,then就是民众,如果疫情被控制住了就分fulfilled, 如果没有抑制,就reject。那么民众(then)就要处理不同的状态。

  • then方法必须返回promise,用户返回或者系统自动返回。

  • 第一个函数在resolve状态时执行,即执行resolve时执行then的第一个函数处理成功的状态

  • 第二个函数在reject状态时执行,即执行reject时执行then的第二个函数处理失败状态,该函数是可选的。

  • 两个函数都接收promise传出的值做为参数

  • 也可以使用catch来处理失败的状态(上面的最后一个例子)

  • 如果then返回promise,下一个then会在当前promise状态改变后继续执行

    语法说明

then的语法如下,onFulfilled函数处理fulfilled状态。onReject函数处理rejected状态

  • onFulfilled 或 onRejected 不是函数将被忽略
  • 两个函数只会被调用一次
  • onFulfilled在promise执行成功时调用
  • onRejected在promise执行拒绝时调用
promise.then(onFulfilled, onRejected)

基础知识
then会在promise执行完成后执行,then第一个函数在resolve成功状态执行

let promise = new Promise((resolve, reject) => {
  resolve("success");
}).then(
  value => {
    console.log(`解决:${value}`);
  },
  reason => {
    console.log(`拒绝:${reason}`);
  }
);

then中第二个参数在失败状态执行

let promise = new Promise((resolve, reject) => {
  reject("is error");
});
promise.then(
  msg => {
    console.log(`成功:${msg}`);
  },
  error => {
    console.log(`失败:${error}`);
  }
);

如果只关心失败时状态,then的第一个参数传递null

let promise = new Promise((resolve, reject) => {
  reject("is error");
});
promise.then(null, error => {
  console.log(`失败:${error}`);
});

promise传向then的值,如果then没有可处理的函数,会一直向后传递(现实中一般不会这么写)

let p1 = new Promise((resolve, reject) => {
    reject("rejected");
})
.then()
.then(
  null,
  f => console.log(f)
);

下面这里例子 如果 onFulfilled不是函数且promise执行成功,p2执行成功并返回相同值

let promise = new Promise((resolve, reject) => {
  resolve("resolve");
});
let p2 = promise.then();
p2.then().then(resolve => {
  console.log(resolve);
});

如果 onRejected 不是函数且promise拒绝执行,p2 拒绝执行并返回相同值

let promise = new Promise((resolve, reject) => {
  reject("reject");
});
let p2 = promise.then(() => {});
p2.then(null, null).then(null, reject => {
  console.log(reject);
});

链式调用

每次then都是一个全新的promise,默认then返回的状态是fulfilled

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

图片3

每次的then都是一个全新的promise,不要以为上一个promise状态会影响以后then返回的状态

let p1 = new Promise(resolve=>{
  resolve();
});
let p2 = p1.then(()=>{
  console.log('这是对p1进行处理')
});
p2.then(()=>{
  console.log('这是对p2进行处理')
});
console.log(p1); // Promise {<resolve>}
console.log(p2); // Promise {<pending>}

# 再试试把上面两行放在 setTimeout里
setTimeout(() => {
  console.log(p1); // Promise {<resolved>}
  console.log(p2); // Promise {<resolved>}
});

下面这个例子 then 是对上个promise 的rejected 的处理,每个 then 会是一个新的promise,默认传递 fulfilled状态

new Promise((resolve, reject) => {
  reject();
})
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
)
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
)
.then(
  resolve => console.log("fulfilled"),
  reject => console.log("rejected")
);
  
/*执行结果*/
  rejected
  fulfilled
  fulfilled

在看下面一个例子

new Promise((resolve,reject)=>{
            reject('error')
        }).then(res=>console.log(res),error=>{console.log(error);})
        .then(res=>console.log(res),err=>console.log(err))
/*执行结果*/
error
undifine

在看一个例子

new Promise((resolve,reject)=>{
            reject('error')
        }).then(res=>console.log(res),error=>{console.log(error);return 123})
        .then(res=>console.log(res),err=>console.log(err))
/*执行结果*/
error
undifine

在上面我已经介绍过在 promise实例中,如果状态改变为fulfilled,则可以返回resolve函数,然后做为参数传到then中成功处理函数中,如果状态为rejected,则可以传到reject函数,然后作为参数传递到then中的拒绝函数中处理。而在then方法中是没有JS部署好的resolve和reject函数的。再往上的第四个例子红可以看到P2的状态是pending的。但是我们可以字then方法中通过设置返回值,创建新的Promise对象之后将返回值传递到下一个then方法中的成功或拒绝函数中去,如果没有返回值则默认为undefined。就是上面两个例子所要表述的内容。

如果内部返回promise时,将使用该promise

let p1 = new Promise(resolve=>{
    resolve();
});
let p2 = p1.then(()=>{
    return new Promise((resolve,reject)=>{
        resolve('内部中的Promise');
    })
});
p2.then(res=>{
    console.log(res);
}); // 内部中的Promise

如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理,需要等待该 promise 变更状态后执行。

let promise = new Promise(resolve => resolve());
let p1 = promise.then(() => {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(`p1`);
      resolve();
    }, 2000);
  });
}).then(() => {
  return new Promise((a, b) => {
    console.log(`p2`);
  });
});

Thenables

包含 then 方法的对象就是一个 promise ,系统将传递 resolvePromise 与 rejectPromise 做为函数参数。

文章开头的地方使用Promise复写的复工通知就是用的该方法。

当然类也可以

new Promise((resolve, reject) => {
  resolve(
    class {
      static then(resolve, reject) {
        setTimeout(() => {
          resolve("解决状态");
        }, 2000);
      }
    }
  );
}).then(
  res => {
    console.log(`fulfilled: ${res}`);
  },
  err => {
    console.log(`rejected: ${err}`);
  }
);

如果对象中的 then 不是函数,则将对象做为值传递

new Promise((resolve, reject) => {
  resolve();
})
.then(() => {
  return {
    then: "后盾人"
  };
})
.then(v => {
  console.log(v);
});

利用Promise用原声JS封装AJAX

function request(url){
            return new Promise((resolve,reject)=>{
                let XHR = XMLHttpRequest ? new XMLHttpRequest(): new window.ActiveXObject('Microsoft.XMLHTTP');
                XHR.onreadystatechange = funciton(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }
                    }else{
                        reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
                    }
                }
                XHR.open('GET', url, true);
        XHR.send(null);
            })
        }

catch

除了正常使用reject,Promise里面发生任何的异常,都会触发失败的状态。

下面使用未定义的变量会触发失败状态

let promise = new Promise((resolve, reject) => {
  abc;
}).then(
  value => console.log(value),
  reason => console.log(reason)
);

如果onFulfilledonReject抛出异常,则p2拒绝执行并返回拒绝原因

let promise = new Promise((resolve,reject)=>{
    throw new Error('fail');
});
let p2 = promise.then();
p2.then().then(null,reject=>{
    console.log(reject);
})

catch用于失败状态的处理函数,等同于 then(null,reject){}

  • 建议使用catch处理错误
  • catch放在最后面用于统一处理前面发生的错误
const promise = new Promise((resolve, reject) => {
  reject(new Error("Notice: Promise Exception"));
}).catch(msg => {
  console.error(msg);
});

catch可以捕获之前所有的promise的错误,所以将catch放在最后。下面中例子中catch捕获了第一个个then返回的promise的错误。

new Promise((resolve, reject) => {
  resolve();
})
.then(() => {
  return new Promise((resolve, reject) => {
    reject(".then ");
  });
})
.then(() => {})
.catch(msg => {
  console.log(msg);
});

错误是冒泡的操作的,下面没有任何一个then 定义第二个函数,将一直冒泡到 catch 处理错误

new Promise((resolve,reject)=>{
    reject(new Error('请求失败'));
})
.then(msg=>{})
.then(msg=>{})
.catch(error=>{
    console.log(error)
})

catch 也可以捕获对 then 抛出的错误处理

new Promise((resolve, reject) => {
  resolve();
})
.then(msg => {
  throw new Error("这是then 抛出的错误");
})
.catch((error) => {
  console.log(error);
});

建议将错误交给catch处理,而不是在 then中完成。

处理机制

promise中抛出的错误也会在catch中捕获,其实可以理解为在内部自动执行了try...catch...

new Promise((resolve, reject) => {
  try {
    throw new Error("fail");
  } catch (error) {
    reject(error);
  }
}).catch(msg => {
  console.log(msg);
});

但像下面的在异步中 throw 将不会触发 catch,而使用系统错误处理

new Promise((resolve, reject) => {
  setTimeout(() => {
    throw new Error("fail");
  }, 2000);
}).catch(msg => {
  console.log(msg + "异步处理");
});

已经处理过的将不会在catch重复处理

new Promise((resolve,reject)=>{
  reject('error')
}).then(null,error=>{
  console.log(error);
}).then()
  .catch(error=>{
  console.log(error)
})

catch 中发生的错误也会抛给最近的错误处理

new Promise((resolve, reject) => {
  reject();
})
.catch(msg => {
  throw new Error('error')
})
.then(null, error => {
  console.log(error);
});

定制错误

可以根据不同的错误类型进行定制操作,下面将参数错误与404错误分别进行了处理

<script type="text/javascript">
        class ParamError extends Error{
            constructor(message){
                super(message);
                this.name = 'ParamError'
            }
        }
        class HttpError extends Error{
            constructor(message){
                super(message);
                this.name = 'HttpError'
            }
        }
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('请求地址错误');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = funciton(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.statux === 404){
                            reject(new HttpError('网址不存在'));
                        }
                    }else{
                        reject(new Error('"Request was unsuccessful: " + XHR.statusText'));
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
        request('http://www.abc.com')
        .then(response=>{
            console.log(response)
        })
        .catch(error=>{
            if(error instanceof ParamError){
                console.log(error.message)
            }
            if(error instanceof HttpError){
                alert(error.message);
            }
            console.log(error)
        })
    </script>

finally

无论状态是resolvereject 都会执行此动作,finally 与状态无关。

const promise = new Promise((resolve, reject) => {
  reject("hdcms");
})
.then(msg => {
  console.log("resolve");
})
.catch(msg => {
  console.log("reject");
})
.finally(() => {
  console.log("resolve/reject状态都会执行");
});

下面使用 finally 处理加载状态,当请求完成时移除加载图标。

request('http://localhost:8080')
.then(response=>{
  console.log(response)
})
.catch(error=>{
  console.log(error)
})
.finally(()=>{
  this.loading = false;
})

实例操作

下面是封装的timeout 函数,使用定时器操作更加方便

function timeout(times) {
  return new Promise(resolve => {
    setTimeout(resolve, times);
  });
}

timeout(3000)
  .then(() => {
    console.log("3秒后执行");
    return timeout(1000);
  })
  .then(() => {
    console.log("执行上一步的promise后1秒执行");
  });

封闭 setInterval 定时器并实现动画效果

<body>
  <style>
    div {
      width: 100px;
      height: 100px;
      background: yellowgreen;
      position: absolute;
    }
  </style>
  <div></div>
</body>
<script>
  function interval(delay = 1000, callback) {
    return new Promise(resolve => {
      let id = setInterval(() => {
        callback(id, resolve);
      }, delay);
    });
  }
  interval(100, (id, resolve) => {
    const div = document.querySelector("div");
    let left = parseInt(window.getComputedStyle(div).left);
    div.style.left = left + 10 + "px";
    if (left >= 200) {
      clearInterval(id);
      resolve(div);
    }
  }).then(div => {
    interval(50, (id, resolve) => {
      let width = parseInt(window.getComputedStyle(div).width);
      div.style.width = width - 10 + "px";
      if (width <= 20) {
        clearInterval(id);
      }
    });
  });
</script>

接口扩展

resolve

使用 promise.resolve 方法可以快速的返回一个promise对象

Promise.resolve('success').then(value=>{
    console.log(value);
})

下面将请求结果缓存,如果再次请求时直接返回带值的 promise

<script type="text/javascript">
        const api = 'http://localhost:3000';
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('请求地址错误');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = function(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.status === 404){
                            reject(new HttpError('网址不存在'));
                        }else{
                            reject(new Error("Request was unsuccessful: " + XHR.statusText));
                        }
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
</script>
<script type="text/javascript">
        function queryAllNumbers(){
            const cache = queryAllNumbers.cache || (queryAllNumbers.chche = new Map());
            if(cache.has('numbers')){
                console.log('走缓存了');
                return Promise.resolve(cache.get('numbers'))
            }
            return request(`${api}/User/GetClassNumber`)
            .then(response=>{
                cache.set('numbers',response.Data);
                console.log('没走缓存');
                return response.Data;
            })
        }
        queryAllNumbers()
        .then(response=>{
            console.log(response)
        })
    </script>

reject

Promise.resolve 类似,reject 生成一个失败的promise

Promise.reject("fail").catch(error => console.log(error));

下面使用 Project.reject 设置状态

new Promise(resolve => {
  resolve("ac=bc");
})
.then(v => {
  if (v != "123") return Promise.reject(new Error("fail"));
})
.catch(error => {
  console.log(error);
});

all

使用Promise.all 方法可以同时执行多个并行异步操作,比如页面加载时同进获取课程列表与推荐课程。

  • 任何一个 Promise 执行失败就会调用 catch方法
  • 适用于一次发送多个异步操作
  • 参数必须是可迭代类型,如Array/Set
  • 成功后返回 promise 结果的有序数组

下例中当 p1、p2 两个 Promise 状态都为 fulfilled时,ps状态才为fulfilled

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第一个Promise");
  }, 1000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第二个Promise");
  }, 1000);
});
let ps = Promise.all([p1, p2])
  .then(results => {
    console.log(results);
  })
  .catch(msg => {
    console.log(msg);
  });

根据用户名获取用户,有任何一人用户获取不到时 promise.all 状态失败,执行 catch 方法

<script type="text/javascript">
        function request(url){
            return new Promise((resolve,reject)=>{
                if(!/^http/.test(url)){
                    throw new ParamError('请求地址错误');
                }
                let XHR = new XMLHttpRequest();
                XHR.onreadystatechange = function(){
                    if(XHR.readyState === 4){
                        if(XHR.status >=200 && XHR.status < 300 || XHR.status === 304){
                            try{
                                let response = JSON.parse(XHR.responseText);
                                resolve(response);
                            }catch(e){
                                reject(e);
                            }
                        }else if(XHR.status === 404){
                            reject(new HttpError('网址不存在'));
                        }else{
                            reject(new Error("Request was unsuccessful: " + XHR.statusText));
                        }
                    }
                }
                XHR.open('GET',url,true);
                XHR.send(null);
            })
        }
        const api = 'http://localhost:3000';
        const promises = ['zhangsan','lisi'].map(name=>{
            return request(`${api}/Course/GetCourseInfoById?name=${name}`)
        })
        Promise.all(promises)
        .then(response=>{
            console.log(response);
        })
        .catch(error=>{
            console.log('Error:' + error);
        })
    </script>

图片4

可以将其他非promise 数据添加到 all 中,它将被处理成 Promise.resolve

const promises = [
  ajax(`${api}/user?name=zhangsan`),
  ajax(`${api}/user?name=lisi`),
  { id: 3, name: "wangwu", email: "wangwu@qq.com" }
];

如果某一个promise没有catch 处理,将使用promise.all 的catch处理

let p1 = new Promise((resolve, reject) => {
  resolve("fulfilled");
});
let p2 = new Promise((resolve, reject) => {
  reject("rejected");
});
Promise.all([p1, p2]).catch(reason => {
  console.log(reason);
});

allSettled

allSettled 用于处理多个promise ,只关注执行完成,不关注是否全部执行成功,allSettled 状态只会是fulfilled

下面的p2 返回状态为 rejected ,但promise.allSettled 不关心,它始终将状态设置为 fulfilled

let p1 = new Promise((resolve, reject) => {
  resolve("resolved");
});
let p2 = new Promise((resolve, reject) => {
  reject("rejected");
});
Promise.allSettled([p1, p2])
.then(msg => {
  console.log(msg);
})

下面是获取用户信息,但不关注某个用户是否获取不成功

let promises = [
  ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
  ajax(`${api}/User/GetCourseInfoById?name=lisi`)
];
Promise.allSettled(promises).then(response => {
  console.log(response);
});

race

使用Promise.race() 处理容错异步,和race单词一样哪个Promise快用哪个,哪个先返回用哪个。

  • 以最快返回的promise为准
  • 如果最快返加的状态为rejected 那整个promiserejected执行catch
  • 如果参数不是promise,内部将自动转为promise

下面将第一次请求的异步时间调整为两秒,这时第二个先返回就用第二人。

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第一个Promise");
  }, 2000);
});
let p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("第二个Promise");
  }, 1000);
});
Promise.race([p1, p2])
.then(results => {
  console.log(results);
})
.catch(msg => {
  console.log(msg);
});

获取用户信息,如果两秒内没有结果 promise.race 状态失败,执行catch 方法

let promises = [
  ajax(`${api}/User/GetCourseInfoById?name=zhangsan`),
  new Promise((a, b) =>
    setTimeout(() => b(new Error("request fail")), 2000)
  )
];
Promise.race(promises)
.then(response => {
  console.log(response);
})
.catch(error => {
  console.log(error);
});

任务队列

如果 then 返回promise 时,后面的then 就是对返回的 promise 的处理

下面使用 forEach 构建的队列,有以下几点需要说明

  • then 内部返回的 promise 更改外部的 promise 变量
  • 为了让任务继承,执行完任务需要将 promise 状态修改为 fulfilled
function queue(nums) {
  let promise = Promise.resolve();
  nums.map(n => {
    promise = promise.then(v => {
      return new Promise(resolve => {
        console.log(n);
        resolve();
      });
    });
  });
}

queue([1, 2, 3, 4, 5]);

下面再来通过 reduce 来实现队列

function queue(nums) {
  return nums.reduce((promise, n) => {
    return promise.then(() => {
      return new Promise(resolve => {
        console.log(n);
        resolve();
      });
    });
  }, Promise.resolve());
}
queue([1, 2, 3, 4, 5]);

async/await 语法糖

使用 async/await 是promise 的语法糖,可以让编写 promise 更清晰易懂,也是推荐编写promise 的方式。

  • async/await 本质还是promise,只是更简洁的语法糖书写
  • async/await 使用更清晰的promise来替换 promise.then/catch 的方式

async

下面在 foo 函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。

下面在 foo 函数前加上async,函数将返回promise,我们就可以像使用标准Promise一样使用了。

async function foo() {
  return "foo foo foo";
}
console.log(hd());
foo().then(value => {
  console.log(value);
});

如果有多个await 需要排队执行完成,我们可以很方便的处理多个异步队列

async function foo(message) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(message);
    }, 2000);
  });
}
async function run() {
  let h1 = await foo("第一次输出");
  console.log(h1);
  let h2 = await foo("第二次输出");
  console.log(h2);
}
run();

await

使用 await 关键词后会等待promise 完成

  • await 后面一般是promise,如果不是直接返回
  • await 必须放在 async 定义的函数中使用
  • await 用于替代 then 使编码更优雅

下例会在 await 这行暂停执行,直到等待 promise 返回结果后才继执行。

async function foo(message){
    let p =  new Promise(resolve=>{
        setTimeout(()=>{
            resolve(message)
        },2000)
    })
    let result = await p;
    console.log(result);
}
foo('输出输出');

一般await后面是外部其它的promise对象

async function foo(message) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(message);
    }, 2000);
  });
}
async function run() {
  let h1 = await foo("第一次输出");
  console.log(h1);
  let h2 = await foo("第二次输出");
  console.log(h2);
}
run();

错误处理

async 内部发生的错误,会将必变promise对象为rejected 状态,所以可以使用catch 来处理

async function foo(){
    console.log(bar);
}
foo().catch(error=>{
    console.log(error);
})

多个 await 时当前面的出现失败,后面的将不可以执行

async function foo() {
  await Promise.reject("fail");
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

如果对前一个错误进行了处理,后面的 await 可以继续执行

async function foo() {
  await Promise.reject("fail").catch(e => console.log(e));
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

也可以使用 try...catch 特性忽略不必要的错误

async function foo() {
  try {
    await Promise.reject("fail");
  } catch (error) {}
  await Promise.resolve("success").then(value => {
    console.log(value);
  });
}
foo();

并发执行
有时需要多个await 同时执行,有以下几种方法处理,下面多个await 将产生等待

async function p1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("houdunren");
      resolve();
    }, 2000);
  });
}
async function p2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("hdcms");
      resolve();
    }, 2000);
  });
}
async function foo() {
  await p1();
  await p2();
}
foo();

使用 Promise.all() 处理多个promise并行执行

async function foo() {
  await Promise.all([p1(), p2()]);
}
foo();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容