# Promise

promise是一个构造函数,用来封装一个异步操作并可以获取其结果。

new Promise( function(resolve, reject) {...});
$(document).ready(function(){
      //获取第一页数据
      $.getJSON("json/poster.json?page=1",function(result){
       attachPoster(result);
       //获取第二页数据
       $.getJSON("json/poster.json?page=2",function(result){
          attachPoster(result);
      	  ...
      });
     });
    });  

这叫做"回调地狱"。上面的例子,改写:

function getPoster(page){
      const promise = new Promise(function(resolve,reject){
        $.getJSON("json/poster.json?page="+page,function(result){
          resolve(result);
        })
      });
      return promise;
    }
    getPoster(1).then(function(result){//获取第一页
      attachPoster(result); 
      return getPoster(2);
    }).then(function(result){//获取二页
      attachPoster(result); 
      return getPoster(3);
    }).then(funciton(result){//获取第三页 ...})

比层层嵌套更清晰,更符合逻辑。Promise就是 解决回调函数嵌套 的一种方案。

new Promise((resolve, reject) => {
    resolve({
        name: "第1个传递的值"
    });
}).then((result) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(result)
            resolve({
                name: "第2个传递的值"
            })
        }, 200)
    })
}).then((result) => {
	console.log(result)
    return 2020
}).then((result) => {
	console.log(result)
})
//{name: "第1个传递的值"}
//{name: "第2个传递的值"}
//2020

如果then是个promise则把resolve/reject传给下个then如果没有promise,则会把return值给下一个thende的第一个函数,如果没有return则下一个then接受为undefined

promise (opens new window)

# then与resolve

then的入参函数,就是resovle的回调方法。相当于callback作为入参的回调,只不过用了then的属性方法传入的。如嵌套多层,then的链式调用就发挥巨大优先性了,它能把层层嵌套平铺开。

# 幂等性 Idempotent

Promise.resolve是个幂等方法,而Promise.reject不是

foo(x) 将产生与 foo(foo(x))、foo(foo(foo(x))) 等相同的输出

let p = Promise.resolve(7);

setTimeout(console.log, 0, p === Promise.resolve(p));
// true

setTimeout(console.log, 0, p === Promise.resolve(Promise.resolve(p)));
// true
GET /pageX HTTP/1.1是幂等的。连续调用多次,客户端接收到的结果都是一样的:
GET /pageX HTTP/1.1   


POST /add_row HTTP/1.1不是幂等的。如果调用多次,就会增加多行记录:
POST /add_row HTTP/1.1
POST /add_row HTTP/1.1   -> Adds a 2nd row

DELETE /idX/delete HTTP/1.1是幂等的,即便是不同请求之间接收到的状态码不一样:
DELETE /idX/delete HTTP/1.1   -> Returns 200 if idX exists
DELETE /idX/delete HTTP/1.1   -> Returns 404 as it just got deleted

# reject

const promise = new Promise(function(resolve,reject){  
       somethingDO();  
       if (/*结果符合预期,异步操作成功*/) {  
            resolve()  
       }else/*不符合预期,操作失败*/  
       {  
           reject();  
       }  
    })

Pomise有三种状态,分别是pending(进行中),resolved(已成功),rejected(已失败),一旦达到相应的状态,就会回调相应的方法。其实称作已成功,或者已失败并不准确,ES6中标准说法fullfiled,rejected。

对应的,then方法有两个入参,分别实现resolved,rejected的回调方法。

promise.then(function(value) {
  // resolved
}, function(error) {
  // rejected
});

继续上面的实例,我们在方法增加控制,生成一个1-10的随机方法,如果大于5就表示失败。

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
           reject(num);
        }
        
      },2000);
      });
      return p;
    }
 
    getPObj(1).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(2);
     },function(data){
      console.log("执行回调方法失败:"+data);
    }).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(3);
     },function(data){
      console.log("执行回调方法失败:"+data);
     }).then(function(data){
      console.log("执行回调方法:"+data);
     },function(data){
      console.log("执行回调方法失败:"+data);
    });

如果返回失败,我们希望终止掉整个链条,但是从实际结果看,是继续往下执行。这是因为,回调第一个reject的方法后,没有返回值,Promise会自动返回一个undefined,传入下一个链条的resolve方法中,并继续后面的then链。

# catch

try...catch是常用捕获异常的方法,在promise对象中也有catch的方法。用来捕获then回调方法中抛出的各类异常,用法如下:

p.then(function(){
         ...
}).catch(e){
          ....
}

catch方法实际就是实现了全局的reject方法。实际开发,一般采用catch代替reject。

# finally

try...catch...finally是黄金组合。finally表示无论什么状态,必定都会执行。

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          reject(new Error("出错了"));
        } 
      },2000);
      });
      return p;
    }
     
    getPObj(1).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(2);
     }).then(function(data){
      console.log("执行回调方法:"+data);
       return getPObj(3);
     }).then(function(data){
      console.log("执行回调方法:"+data);
     }).catch(function(e){
        console.log(e);
    }).finally(function(){
      console.log("finally");
    });

# all

Promise.all可以将几个Promise对象封装成一个,格式如下:

Promise.all([p1,p2,p3]).then(function(data){...})

当这几个对象都变成resolved状态后,总状态变为resolved;否则,其中有一个为rejected状态,则变成reject,其他的可以忽略。可以理解为p1&&p2&&p3。

那返回的data是什么样子, 如果是resolved状态,则是各个对象data的组合;如果是rejected,则是第一个到达rejected状态返回的data值。 以例为证。

function getPObj(num){
  var p = new Promise(function(resolve,reject){
	setTimeout(function(){
	 console.log("开始执行定时器:"+num);
	 if((Math.random()*10)>3){
			 resolve(num);
	 }else{
		 reject(num)
	 }
	
  },2000);
  });
  return p;
}
 
Promise.all([getPObj(1),getPObj(2),getPObj(3)]).then(function(data){
		console.log("resolve");
		console.log(data);
	 }).catch(function(e){
		 console.log("error");
		 console.log(e);
})

执行的结果:

//概率问题
开始执行定时器:1
error
1
开始执行定时器:2
开始执行定时器:3
--------------------------------
开始执行定时器:1
开始执行定时器:2
开始执行定时器:3
resolve
(3) [1, 2, 3]
const timer = a => { 
	  return new Promise(res => 
	    setTimeout(() => { 
	      res(a); 
	    }, Math.random() * 100) 
	  ); 
	}; 
	 
	const all = Promise.all([ 
	  timer('first'), 
	  timer('second') 
	]).then(data => console.log(data)); 
 //["first", "second"]

Promise解决的顺序与 Promise.all 无关。可以可靠地指望它们以数组参数中提供的相同顺序返回。

# race

Promise.race([p1,p2,p3]).then(function(data){...})

与all不同的是,看谁执行的快,then就回到回调谁的结果。可以理解为p1||p2||p3

看实例:

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          reject(num);
        } 
      },2000);
      });
      return p;
    }
 
Promise.race([getPObj(1),getPObj(2),getPObj(3)]).then(function(data){
		console.log("resolve");
		console.log(data);
	 }).catch(function(e){
		 console.log("error");
		 console.log(e);
	 })

执行结果(由于随机数,各位执行的结果会有不一样的情况):

开始执行定时器:1
resolve
1
开始执行定时器:3
----------------------------------
开始执行定时器:1
error
开始执行定时器:2
开始执行定时器:3

当接受到第一个对象的resolved状态后,其他的两个抛弃处理。

# trycatch与promise

try {
  throw new Error('foo');
} catch(e) {
  console.log(e);  // Error: foo
}

try {
  Promise.reject(new Error('bar'));
  console.log('1')
} catch(e) {
  //promise的抛出的reject不走这里
  console.log(e);
  console.log('2')
}


// Error: foo   
// 1
// Uncaught (in promise) Error: bar
	  
  • try 的 catch 和 promise 的 catch 有什么区别?

一、相同点,都是获取代码错误信息的方法,而且都不能获取异步错误

  1. try… catch…
	 try {
	  console.log(a)
	 } catch (error) {
	  console.log(error)	// ReferenceError: a is not defined
	 }
	 try {
	  setTimeout(() => {
	   console.log(a)
	   }, 100)
	 } catch (error) {
	 console.log(error)
	 }

	// 浏览器报错:Uncaught ReferenceError: a is not defined
  1. Promise… catch…
	new Promise((resolve, reject) => {
	  console.log(a)
	}).catch(error => {
	  console.log(error)	// ReferenceError: a is not defined	
	})
new Promise((resolve, reject) => {
  setTimeout(() => {
     console.log(a)
	  }, 100)
}).catch(error => {
  console.log(error) 
})
// 浏览器报错:Uncaught ReferenceError: a is not defined

二、不同点:try… catch… 不能捕获 Promise 里 reject 出来的错误信息

 try {
  Promise.reject('出错了');
 } catch(e) {
  console.log(e)
 }
 // 浏览器报错:Uncaught (in promise) 出错了

Promise 里 reject 出来的错误信息,可用 Promise.catch() 的方法来获取

try {
	Promise.reject('出错了')
	.catch(err => { console.log('2', err) });
	console.log(a);
} catch (error) {
	console.log(error)
}
// ReferenceError: a is not defined
// 2 出错了

能被 try catch 捕捉到的异常,必须是在报错的时候,线程执行已经进入 try catch 代码块,且处在 try catch 里面,这个时候才能被捕捉到。

try...catch (opens new window)

# 实例

new Promise((resolve,reject)=>{
	// resolve(1000)
	reject(10)
})
// //不注释的话,下面的catch都会不走,resolve1的res为undefined
// .then(res=>{
// 	console.log('第一个方法resolve',res)
// },res=>{
// 	console.log('第二个方法reject',res)
// })
.catch(e=>{
	console.log('reject1',e)
	return Promise.resolve(7777)
}).catch(e=>{
	console.log('reject2',e)
	return Promise.resolve(8888)
}).finally(e=>{
	console.log('finally1',e)
})
.then(res=>{
	console.log('resolve1',res)
}).finally(e=>{
	console.log('finally2',e)
})


// reject1 10
// finally1 undefined
// resolve1 7777
// finally2 undefined

# $.ajax与promise

jquery封装的ajax本身就是最终结果就是一个promise,经过测试,在老版本中不支持catch模式推出异常,但是支持在then的第二个函数抛出错误,比较新的版本如3.x版本是支持catch报异常!

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
    <title>ajax</title>
	<style type="text/css">
	
	</style>
</head>
<body>
<!-- <script src="https://libs.baidu.com/jquery/1.11.1/jquery.min.js"></script> -->
<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<!-- <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> -->
<script type="text/javascript">
var val=$.ajax({
	url:'https://douban.uieee.com/v2/movie/in_theaters',
})


val.then(res=>{
	console.log(res)
},err=>{
	console.log(`err`)
	console.log(err)
})
val.then(res=>{
	console.log(res)
}).catch((err)=>{
	console.log(`err`)
	console.log(err)
})
</script>
</body>
</html>
var val=$.ajax({
	url:'https://douban.uieee.com/v2/movie/in_theaters',
})

var val1=$.ajax({
	url:'https://douban.uieee.com/v2/movie/coming_soon',
})

Promise.all([val,val1]).then(res=>{
	console.log(res)
	// (2) [{…}, {…}]
	// 0: {count: 20, start: 0, total: 62, subjects: Array(20), title: "正在上映的电影-北京"}
	// 1: {count: 20, start: 0, total: 45, subjects: Array(20), title: "即将上映的电影"}
	// length: 2
	// __proto__: Array(0)
}).catch(err=>{
	console.log(`err`)
	console.log(err)
})

# async/await异步函数

async 和 await 可以说是异步终极解决方案了,相比直接用 Promise ,可以用同步的方式写异步的回调,也不需要.then这种形式。

async函数返回值一个Promise,这个promise要么会通过一个由async函数返回的值被解决,要么会通过一个从async函数中抛出的(或其中没有被捕获到的)异常被拒绝。

return 的值会被Promise.resolve()包装成promise对象

async function fun(){
	
}
let s = fun()
console.log(s)//Promise {<fulfilled>: undefined}
async function fun1(){
	return 10
}
let s1 =fun1()
console.log(s1)//Promise {<fulfilled>: 10}
async function foo() {
  console.log(1);
  return 3;
}

// Attach a resolved handler to the returned promise
foo().then(console.log);

console.log(2);
//1 2 3

当然,直接返回一个promise对象也是一样的

async function foo() {
  console.log(1);
  return Promise.resolve(3);
}

// Attach a resolved handler to the returned promise
foo().then(console.log);

console.log(2);
//1 2 3

异步函数的返回值期待(不是必须)一个实现了thenable的接口,但常规值也可以。如果返回的是实现thenable接口的对象,则这个对象可以由提供给then()的处理程序“解包”。如果不是,则返回值就被当做已经解决的promise

// Return a primitive
async function foo() {
  return 'foo';
}
foo().then(console.log);
// foo

// Return a non-thenable object
async function bar() {
  return ['bar'];
}
bar().then(console.log);
// ['bar']

// 实现一个thenable非期约函数
async function baz() {
  const thenable = {
    then(callback) { callback('baz'); }
  };
  return thenable;
}
baz().then(console.log);
// baz

// Return a promise
async function qux() {
  return Promise.resolve('qux');
}
qux().then(console.log);
// qux

等待会抛出错误的同步操作,会返回拒绝的期约:

async function foo() {
  console.log(1);
  throw 3;
}

// Attach a rejected handler to the returned promise
foo().catch(console.log);
console.log(2);

// 1
// 2
// 3
async function foo() {
	await Promise.reject(10)
}

// Attach a rejected handler to the returned promise
foo().catch(console.log);
console.log(1);


async function foo1() {
	return Promise.reject(20)
}

// Attach a rejected handler to the returned promise
foo1().catch(console.log);
console.log(2);
// 1
// 2
// 10
// 20

单独的Promise.reject()不会被异步函数捕获,而会抛出未捕获错误。不过,对拒绝的期约使用await/return 则会释放错误值

async function foo() {
	Promise.reject(10)
}

// Attach a rejected handler to the returned promise
foo().catch(console.log);
console.log(1);

// 1
// Uncaught (in promise) 10

async函数可能包含0个或者多个await表达式。await表达式会暂停整个async函数的执行进程并出让其控制权,只有当其等待的基于promise的异步操作被兑现或被拒绝之后才会恢复进程 。promise的解决值会被当作该await表达式的返回值。使用async / await关键字就可以在异步代码中使用普通的try / catch代码块。

async function test() {
  // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
  // 如果有依赖性的话,其实就是解决回调地狱的例子了
  await fetch(url)
  await fetch(url1)
  await fetch(url2)
}
function getSyncTime() {
  return new Promise((resolve, reject) => {
    try {
      let startTime = new Date().getTime()
      setTimeout(() => {
        let endTime = new Date().getTime()
        let data = endTime - startTime
        resolve( data )
      }, 500)
    } catch ( err ) {
      reject( err )
    }
  })
}

async function getSyncData() {
  let time = await getSyncTime()
  let data = `endTime - startTime = ${time}`
  return data
}

async function getData() {
  let data = await getSyncData()
  console.log( data )
}

getData()
  • 可以让异步逻辑用同步写法实现,当然,只是在这个async中,并不阻塞其他函数的执行
  • 最底层的await返回需要是Promise对象
  • 可以通过多层 async function 的同步写法代替传统的callback嵌套
let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a) // -> '2' 10
}
b()
a++
console.log('1', a) // -> '1' 1
//先 "1" 1再 "2" 10
首先函数 b 先执行,在执行到 await 10 之前变量 a 还是 0,因为 await 内部实现了 generator ,
generator 会保留堆栈中东西,所以这时候 a = 0 被保存了下来因为 await 是异步操作,
后来的表达式不返回 Promise 的话,就会包装成 Promise.reslove(返回值),然后会去执行函数外的同步代码
同步代码执行完毕后开始执行异步代码,将保存下来的值拿出来使用,这时候 a = 0 + 10

async函数一定会返回一个promise对象。如果一个async函数的返回值看起来不是promise,那么它将会被隐式地包装在一个promise中。

async function foo() {
   return 1
}

等价以下代码

function foo() {
   return Promise.resolve(1)
}

async函数的函数体可以被看作是由0个或者多个await表达式分割开来的。从第一行代码直到(并包括)第一个await表达式(如果有的话)都是同步运行的。这样的话,一个不含await表达式的async函数是会同步运行的。然而,如果函数体内有一个await表达式,async函数就一定会异步执行。

async function foo() {
   await 1
}

等价以下代码

function foo() {
   return Promise.resolve(1).then(() => undefined)
}

# await处理期约

await后面跟期约,为了执行异步函数,实际上会有两个任务被添加到消息对了中,结果如下(新版本浏览器因为TC39对期约情况处理做了修改:123458967)

async function foo() {
  console.log(2);
  console.log(await Promise.resolve(8));
  console.log(9);
}

async function bar() {
  console.log(4);
  console.log(await 6);
  console.log(7);
}

console.log(1);
foo();
console.log(3);
bar();
console.log(5);
// 1
// 2
// 3
// 4
// 5
// 6
// 7
// 8
// 9 

await 后面可以跟任何的JS 表达式。虽然说 await 可以等很多类型的东西,但是它最主要的意图是用来等待 Promise 对象的状态被 resolved。

  • 如果await后面是 promise对象会造成异步函数停止执行并且等待 promise 的解决,
  • 如果await后面是 正常的表达式则立即执行。
function sleep(second) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(' enough sleep~');
        }, second);
    })
}
async function awaitDemo() {
    let result = await sleep(2000);
    console.log("123")
    console.log(result);
}
awaitDemo();// 两秒之后会被打印出来 '123'和' enough sleep~'
function sleep(second) {
    
        setTimeout(() => {
            console.log(' enough sleep~');
        }, second);
   
}
async function awaitDemo() {
    let result = await sleep(2000);
    console.log("123");
}
awaitDemo();//立即输出123  两秒后输出' enough sleep~'

async函数 (opens new window)

# 异步扩展

需求:来实现一个「运动路径动画」流程:移动页面上元素 target先从原点出发,向左移动 20px,之后再向上移动 50px,最后再次向左移动 30px,请把运动动画实现出来。

将移动的过程封装成一个 walk 函数,该函数要接受以下三个参数。

  • direction:字符串,表示移动方向,这里简化为「left」、「top」两种枚举
  • distance:整型,可正或可负
  • callback:动作执行后回调

direction 表示移动方向,distance 表示移动距离。通过 distance 的正负值,我们可以实现四个方向的移动。

  1. 回调地狱法
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<title></title>
	</head>
	<style type="text/css">
		#man{
			width:100px;
			height:100px;
			border:1px solid red;
		}
	</style>
	<body>
		<div id='man'></div>
	</body>
</html>
<script type="text/javascript">
	const target = document.querySelectorAll('#man')[0];
	target.style.cssText = `
	position: absolute;
	left: 0px;
	top: 0px
	`;
	
	const walk = (direction, distance, callback) => {
	  setTimeout(() => {
	    let currentLeft = parseInt(target.style.left, 10);
	    let currentTop = parseInt(target.style.top, 10);
	
	    const shouldFinish =
	      (direction === 'left' && currentLeft === -distance) ||
	      (direction === 'top' && currentTop === -distance);
	
	    if (shouldFinish) {
	      // 任务执行结束,执行下一个回调
	      callback && callback();
	    } else {
	      if (direction === 'left') {
	        currentLeft--;
	        target.style.left = `${currentLeft}px`;
	      } else if (direction === 'top') {
	        currentTop--;
	        target.style.top = `${currentTop}px`;
	      }
	
	      walk(direction, distance, callback);
	    }
	  }, 20);
	};
	
	walk('left', 20, () => {
	  walk('top', 50, () => {
	    walk('left', 30, Function.prototype);
	  });
	});
</script>
  1. promise法
const target = document.querySelectorAll('#man')[0];
target.style.cssText = `
position: absolute;
left: 500px;
top: 500px
`;

const walk = (direction, distance) =>
  new Promise((resolve, reject) => {
    const innerWalk = () => {
      setTimeout(() => {
        let currentLeft = parseInt(target.style.left, 10);
        let currentTop = parseInt(target.style.top, 10);

        const shouldFinish =
          (direction === 'left' && currentLeft === -distance) ||
          (direction === 'top' && currentTop === -distance);

        if (shouldFinish) {
          // 任务执行结束
          resolve();
        } else {
          if (direction === 'left') {
            currentLeft--;
            target.style.left = `${currentLeft}px`;
          } else if (direction === 'top') {
            currentTop--;
            target.style.top = `${currentTop}px`;
          }

          innerWalk();
        }
      }, 20);
    };
    innerWalk();
  });

walk('left', 20)
  .then(() => walk('top', 50))
  .then(() => walk('left', 30));
  1. generator 方案
const target = document.querySelectorAll('#man')[0];
target.style.cssText = `
position: absolute;
left: 0px;
top: 0px
`;

const walk = (direction, distance) =>
  new Promise((resolve, reject) => {
    const innerWalk = () => {
      setTimeout(() => {
        let currentLeft = parseInt(target.style.left, 10);
        let currentTop = parseInt(target.style.top, 10);

        const shouldFinish =
          (direction === 'left' && currentLeft === -distance) ||
          (direction === 'top' && currentTop === -distance);

        if (shouldFinish) {
          // 任务执行结束
          resolve();
        } else {
          if (direction === 'left') {
            currentLeft--;
            target.style.left = `${currentLeft}px`;
          } else if (direction === 'top') {
            currentTop--;
            target.style.top = `${currentTop}px`;
          }

          innerWalk();
        }
      }, 20);
    };
    innerWalk();
  });

function* taskGenerator() {
  yield walk('left', 20);
  yield walk('top', 50);
  yield walk('left', 30);
}
const gen = taskGenerator();

//我们定义了一个 taskGenerator 生成器函数,并实例化出 gen,手动执行:
gen.next() //将会向左偏移 20 像素。

// 再次手动执行:
gen.next() // 将会向上偏移 50 像素。

// 再次手动执行:
gen.next() //将会向左偏移 30 像素。

唯一的不便之处就是需要我们反复手动执行 gen.next()。为此社区上早有方案,kj 大神的 co 库 (opens new window),能够自动包裹 generator 并执行

  1. async/await方案
const target = document.querySelectorAll('#man')[0];
target.style.cssText = `
position: absolute;
left: 0px;
top: 0px
`;

const walk = (direction, distance) =>
  new Promise((resolve, reject) => {
    const innerWalk = () => {
      setTimeout(() => {
        let currentLeft = parseInt(target.style.left, 10);
        let currentTop = parseInt(target.style.top, 10);

        const shouldFinish =
          (direction === 'left' && currentLeft === -distance) ||
          (direction === 'top' && currentTop === -distance);

        if (shouldFinish) {
          // 任务执行结束
          resolve();
        } else {
          if (direction === 'left') {
            currentLeft--;
            target.style.left = `${currentLeft}px`;
          } else if (direction === 'top') {
            currentTop--;
            target.style.top = `${currentTop}px`;
          }

          innerWalk();
        }
      }, 20);
    };
    innerWalk();
  });

const task = async function () {
  await walk('left', 20);
  await walk('top', 50);
  await walk('left', 30);
};

task() // 只需要直接执行 task() 即可。

# 实用案例

请求图片进行预先加载,假设预先有 urlIds 数组,数组的每一项都可以按照规则拼接成一个完整的图片地址。根据这个数组,依次请求图片进行预加载。

const loadImg = urlId => {
   const url = `https://www.image.com/${urlId}`
   return new Promise((resolve, reject) => {
       const img = new Image()
       img.onerror = function() {
           reject(urlId)
       }
       img.onload = function() {
           resolve(urlId)
       }
       img.src = url
   })
}

该方法进行 promise 化(promisify),在图片成功加载时进行 resolve,加载失败时 reject。

依次请求图片:

const urlIds = [1, 2, 3, 4, 5]
urlIds.reduce((prevPromise, urlId) => {
   return prevPromise.then(() => loadImg(urlId))
}, Promise.resolve())

使用了数组 reduce 方法,当然也可以面向过程实现:

const loadImgOneByOne = index => {
   const length = urlIds.length
   loadImg(urlIds[index]).then(() => {
       if (index === length - 1) {
           return
       }
       else {
           loadImgOneByOne(++index)
       }
   })

}
loadImgOneByOne(0)

也可以采用 async/await 实现

const loadImgOneByOne = async () => {
   for (i of urlIds) {
       await loadImg(urlIds[i])
   }
}
loadImgOneByOne()

上述代码的请求都是依次执行的,只有成功加载完第一张图片,才继续进行下一张图片的加载。

如果要求提高效率,将所有图片的请求一次性发出,该如何做呢?

const urlIds = [1, 2, 3, 4, 5]

const promiseArray = urlIds.map(urlId => loadImg(urlId))

Promise.all(promiseArray)
   .then(() => {
       console.log('finish load all')
   })
   .catch(() => {
       console.log('promise all catch')
   })

# promise

  1. 没有执行resolve和reject情况下,不会进入then
new Promise((resolve,reject)=>{
	console.log('promise')
}).then((res)=>{
	console.log('then1.1',res)
})
//promise
  1. 没有执行resolve和reject,人为抛出错误,如果有catch处理异常,会进入catch,没有catch,代码报错
new Promise((resolve,reject)=>{
	console.log('promise')
	try{
		asdhiasfd
	}catch(e){
		ewqeeqwe
	}
	// throw new Error('ewqeeqwe');
}).then((res)=>{
	console.log('then1.1',res)
}).catch(e=>{
	console.log(e)
})
//promise

// ReferenceError: ewqeeqwe is not defined
//     at 000.html:25:3
//     at new Promise (<anonymous>)
//     at 000.html:20:1
  1. 处理异常(手动或者reject),如果then的第二个函数存在,catch将不会接受到异常错误
new Promise((resolve,reject)=>{
	console.log('promise')
	throw new Error('手动错误');
	//reject('xxx')
}).then((res)=>{
	console.log('then1.1',res)
},(reject)=>{
	console.log('then1.2',reject)
}).catch(e=>{
	console.log('catch捕捉',e)
})

//promise

// then1.2 Error: 手动错误
//     at 000.html:23:8
//     at new Promise (<anonymous>)
//     at 000.html:20:1
  1. catch后的流程,如果catch未执行,取catch前面执行的返回值
new Promise((resolve,reject)=>{
	console.log('promise')
	reject(9999)
}).then((res)=>{
	console.log('then1.1',res)
},(reject)=>{
	console.log('then1.2',reject)
	return 100
}).catch(e=>{
	console.log('catch捕捉',e)
	return 1000
}).then((res)=>{
	console.log('catch后面的res',res)
})
//promise
//then1.2 9999
//catch后面的res 100
  1. promise没有reject函数处理,走进catch,catch返回值会传给后面的then
new Promise((resolve,reject)=>{
	console.log('promise')
	reject(9999)
}).then((res)=>{
	console.log('then1.1',res)
}).catch(e=>{
	console.log('catch捕捉',e)
	return 1000
}).then((res)=>{
	console.log('catch后面的res',res)
})

//promise

// catch捕捉 9999
// catch后面的res 1000
  1. 如果有多个catch,被第一个catch捕获后,后续的就不会再捕获这个错误,除非,错误是第一个catch之后;同时resolve()也可能走catch,出错的情况
new Promise((resolve,reject)=>{
	resolve(action)
}).then((res)=>{
	console.log('then1.1',res)
}).catch(e=>{
	console.log('catch捕捉',e)
}).then((res)=>{
	console.log('catch后面的res',res)
}).catch(e=>{
	console.log('第二个catch',e)
})

// catch捕捉 ReferenceError: action is not defined
//     at 000.html:21:10
//     at new Promise (<anonymous>)
//     at 000.html:20:1
//  catch后面的res undefined
  1. 如果promise的错误一直没处理,那么直到处理后才执行,处理前的所有then都不去执行
new Promise((resolve,reject)=>{
	resolve(action)
}).then((res)=>{
	console.log('then1.1',res)
}).then((res)=>{
	console.log('catch后面的res',res)
}).catch(e=>{
	console.log('第二个catch',e)
}).then(res=>{
	console.log('then',res)
})

// 第二个catch ReferenceError: action is not defined
//     at 000.html:21:10
//     at new Promise (<anonymous>)
//     at 000.html:20:1

//then undefined
  1. 中间的then,catch也可以返回新的promise;注意return返回
new Promise((resolve,reject)=>{
	resolve('action')
}).then(res=>{
	new Promise((reslove,reject)=>{
		setTimeout(()=>{
			reslove(10)
		},1000)
	})
}).then(res=>{
	console.log('res',res)
})
//res undefined

new Promise((resolve,reject)=>{
	resolve('action')
}).then(res=>{
	return new Promise((reslove,reject)=>{
		setTimeout(()=>{
			reslove(10)
		},1000)
	})
}).then(res=>{
	console.log('res',res)
})
//res 10


new Promise((resolve,reject)=>{
	resolve('action')
}).then(res=>{
	return new Promise((reslove,reject)=>{
		setTimeout(()=>{
			reject(10)
		},1000)
	})
}).then(res=>{
	console.log('res',res)
},(res)=>{
	console.log('error',res)
})
//error 10
  1. 进入链式then中返回默认是promise resolved的状态,除非触发Promise.reject操作
let a=new Promise((resolve,reject)=>{
	
})
console.log(a)
let b=new Promise((resolve,reject)=>{
	resolve(1)
})
console.log(b)

let c =new Promise((resolve,reject)=>{
	resolve(1)
}).then(res=>{
	return 10
})
console.log(c)

let d =new Promise((resolve,reject)=>{
	resolve(1)
}).then(res=>{
	return new Promise((resolve,reject)=>{
		reject(10)
	})
}).catch(()=>{
	
})
console.log(d)
  1. then处理return处理的值如果是promise会被接管,等待返回的状态后进行下一步
new Promise((resolve,reject)=>{
	resolve(10)
}).then(res=>{
	return new Promise((resolve,reject)=>{
			setTimeout(()=>{
				resolve(1)
			},4000)
	})
	
}).then(res=>{
	console.log(res,'res')
	console.log(2020)
})
//四秒后执行
//1 'res'
//2020
  1. then处理return是定时器等异步,会自动执行下一步,然后再处理,下一个then接受的res?
new Promise((resolve,reject)=>{
	resolve(20)
}).then(res=>{
	return setTimeout(()=>{
		console.log('settimeout')
		return 1
	},5000)
	
	
}).then(res=>{
	console.log(res,'res')
	console.log(2022)
})
// 立刻执行
//2 res //不同的浏览器打印不一样,不一定是2
//2022
// 5秒后执行
// settimeout
  1. then的穿透能力,一个空的then可以把上一个then的返回值传递给下一个

总结:

  1. 如果then中的第二个函数已经处理了错误,那么其后的catch代码就不会再去执行
  2. then中返回值需要加return,否则下一个then接受到的undefined
  3. catch不会阻止其后的代码继续执行
  4. resolve,reject和手动error都可以走catch[如:resolve传递一个不存在的参数,报错了]
  5. then return处理的new Promise会等待结果后再去执行一下个then,如果新promise执行到resolve或者reject,下一个then会去处理返回的结果,如果新promise自己有then,那么最初的promise的下一个then的res为undefined
  6. then return如果处理的是异步如settimeout不会阻止下个then执行,到最后会返回来执行settimeout
最后更新: 4/20/2024, 7:40:18 AM