从零开始的Promise(三)

421 阅读7分钟

从零开始的Promise(一)

从零开始的Promise(二

4. Promise 的几个关键问题

1. 如何改变 promise 的状态?

(1)resolve(value):如果当前是 pending 就会变为 resolved

(2)reject(reason):如果当前是 pending 就会变为 rejected

(3)抛出异常:如果当前是 pending 就会变为 rejected

const p = new Promise((resolve, reject) => {
  //resolve(1) // pending   => fulfilled (resolved)
  //reject(2) // pending  =>  rejected  promise变为rejected失败状态
  throw new Error('出错了') // 抛出异常,promise变为rejected失败状态,reason为抛出的error
})
p.then(
  value => {},
  reason => {console.log('reason',reason)}
)
// reason Error:出错了

2. 一个 promise 指定多个成功/失败回调函数,都会调用吗?

当 promise 改变为对应状态时都会调用

const p = new Promise((resolve, reject) => {
  //resolve(1)
  reject(2)
})
p.then(
  value => {},
  reason => {console.log('reason',reason)}
)
p.then(
  value => {},
  reason => {console.log('reason2',reason)}
)
// reason 2
// reason2 2

3. 改变 promise 状态和指定回调函数谁先谁后?

都有可能,常规是先指定回调再改变状态,但也可以先改状态再指定回调

因为then 可以接两个回调,第一个是成功的,第二个是失败的,这里说的指定,意思是二选一成功还是失败那个回调,但是并不执行,这么说吧:promise对象是同步时,执行里面的resolve或reject然后继续执行then及回调,如果是异步,则先执行then,异步执行完了,改变了状态再执行then里的回调

  • 如何先改状态再指定回调?

    1. 在执行器中同步任务直接调用 resolve()/reject()
    2. 延迟更长时间才调用 then()
let p = new Promise((resolve, reject) => {
  // setTimeout(() => {
      resolve('OK');
  // }, 1000); // 有异步就先指定回调,否则先改变状态
});

p.then(value => {
  console.log(value);
},reason=>{
  
})
  • 什么时候才能得到数据?
    1. 如果先指定的回调,那当状态发生改变时,回调函数就会调用得到数据
    2. 如果先改变的状态,那当指定回调时,回调函数就会调用得到数据
new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1) // 改变状态
  }, 1000)
}).then( // 指定回调函数 (先指定)
  value => {},
  reason =>{}
)

此时,先指定回调函数,保存当前指定的回调函数;后改变状态(同时指定数据),然后异步执行之前保存的回调函数。

new Promise((resolve, reject) => {
  resolve(1) // 改变状态
}).then( // 指定回调函数
  value => {},
  reason =>{}
)

这种写法,先改变的状态(同时指定数据),后指定回调函数(不需要再保存),直接异步执行回调函数

4. promise.then() 返回的新 promise 的结果状态由什么决定?

(1)简单表达:由 then() 指定的回调函数执行的结果决定

let p = new Promise((resolve, reject) => {
  resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
  console.log(value);
}, reason => {
  console.warn(reason);
});

console.log(result);

image.png

(2)详细表达:

​ ① 如果抛出异常,新 promise 变为 rejected,reason 为抛出的异常

let p = new Promise((resolve, reject) => {
  resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
  //1. 抛出错误
  throw '出了问题';
}, reason => {
  console.warn(reason);
});
console.log(result);

image.png

② 如果返回的是非 promise 的任意值,新 promise 变为 resolved,value 为返回的值

let p = new Promise((resolve, reject) => {
  resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
	//2. 返回结果是非 Promise 类型的对象
	return 521;
}, reason => {
  console.warn(reason);
});

console.log(result);

image.png

③ 如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果

let p = new Promise((resolve, reject) => {
  resolve('ok');
});
//执行 then 方法
let result = p.then(value => {
	//3. 返回结果是 Promise 对象
	return new Promise((resolve, reject) => {
		// resolve('success');
		reject('error');
	});
}, reason => {
  console.warn(reason);
});

console.log(result);

image.png

5.promise 如何串联多个操作任务?

(1)promise 的 then() 返回一个新的 promise,可以并成 then() 的链式调用

(2)通过 then 的链式调用串联多个同步/异步任务

let p = new Promise((resolve, reject) => {
  setTimeout(() => {
      resolve('OK');
  }, 1000);
});

p.then(value => {
  return new Promise((resolve, reject) => {
      resolve("success");
  });
}).then(value => {
  console.log(value); // success
}).then(value => {
  console.log(value); // undefined
})

image.png

练习题

new Promise((resolve, reject) => {
  resolve(1)
}).then(
  value => {
    console.log('onResolved1()', value)
  },
  reason => {
    console.log('onRejected1()', reason)
  }
).then(
  value => {
    console.log('onResolved2()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onResolved1() 1
// onResolved2() undefined

image.png

new Promise((resolve, reject) => {
  resolve(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    //return 2                   // onResolved2() 2
    //return Promise.resolve(3)  // onResolved2() 3
    //return Promise.reject(4)   // onRejected2() 4
    //throw 5                    // onRejected2() 5
  },
  reason => {
    console.log('onRejected1()', reason)
  }
).then(
  value => {
    console.log('onResolved2()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onResolved1() 1
// onResolved2() undefined
// Promise {<fulfilled>: undefined}
// 对应输出如上所示

image.png

new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('执行任务1(异步)')
    resolve(1)
  }, 1000)
}).then(
  value => {
    console.log('任务1的结果', value)
    console.log('执行任务2(同步)')
    return 2 // 同步任务直接return返回结果
  }
).then(
  value => {
    console.log('任务2的结果', value)
    return new Promise((resolve, reject) => { // 异步任务需要包裹在Promise对象中
      setTimeout(() => {
        console.log('执行任务3(异步)')
        resolve(3)
      }, 1000)
    })
  }
).then(
  value => {
    console.log('任务3的结果', value)
  }
)
// 执行任务1(异步)
// 任务1的结果 1
// 执行任务2(同步)
// 任务2的结果 2
// 执行任务3(异步)
// 任务3的结果 3

image.png

6.Promise 异常穿透(传透)?

(1)当使用 promise 的 then 链式调用时,可以在最后指定失败的回调

(2)前面任何操作出了异常,都会传到最后失败的回调中处理

   <script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
                // reject('Err');
            }, 1000);
        });

        p.then(value => {
            // console.log(111);
            throw '失败啦!';
        }).then(value => {
            console.log(222);
        }).then(value => {
            console.log(333);
        }).catch(reason => {
            console.warn(reason);
        });
    </script>

例题

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
)
// onRejected1() 1

相当于这种写法:多写了很多reason => {throw reason}

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  },
  reason => {throw reason} // 抛出失败的结果reason
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  },
  reason => {throw reason} // 抛出失败的结果reason
).then(
  value => {
    console.log('onResolved3()', value)
  },
  reason => {throw reason} // 抛出失败的结果reason
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
)
// onRejected1() 1

所以失败的结果是一层一层处理下来的,最后传递到 catch 中。

或者,将 reason => {throw reason} 替换为 reason => Promise.reject(reason) 也是一样的

7.中断 promise 链?

当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数

办法:在回调函数中返回一个 pending 状态的 promise 对象

  <script>
        let p = new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve('OK');
            }, 1000);
        });

        p.then(value => {
            console.log(111);
            //有且只有一个方式
            return new Promise(() => {});
        }).then(value => {
            console.log(222);
        }).then(value => {
            console.log(333);
        }).catch(reason => {
            console.warn(reason);
        });
    </script>

例题

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
  }
).then(
  value => {
    console.log('onResolved4()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onRejected1() 1
// onResolved4() undefined

为了在 catch 中就中断执行,可以这样写:

new Promise((resolve, reject) => {
   //resolve(1)
   reject(1)
}).then(
  value => {
    console.log('onResolved1()', value)
    return 2
  }
).then(
  value => {
    console.log('onResolved2()', value)
    return 3
  }
).then(
  value => {
    console.log('onResolved3()', value)
  }
).catch(
  reason => {
    console.log('onRejected1()', reason)
    return new Promise(() => {}) // 返回一个pending的promise
  }
).then(
  value => {
    console.log('onResolved4()', value)
  },
  reason => {
    console.log('onRejected2()', reason)
  }
)
// onRejected1() 1

在 catch 中返回一个新的 promise,且这个 promise 状态没有改变。

由于,返回的新的 promise 结果决定了后面 then 中的结果,所以后面的 then 中也没有结果。

这就实现了中断 promise链的效果。

5. async await

5.1 async

  1. 函数的返回值为 promise 对象
  2. promise 对象的结果由 async 函数执行的返回值决定
① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常
② 如果返回的是非promise的任意值, 新promise变为resolved, value为返回的值
③ 如果返回的是另一个新promise, 此promise的结果就会成为新promise的结果
    <script>
        //和then是一样的
        async function main(){
            //1. 如果返回值是一个非Promise类型的数据
            // return 521;
            //2. 如果返回的是一个Promise对象
            // return new Promise((resolve, reject) => {
            //     // resolve('OK');
            //     reject('Error');
            // });
            //3. 抛出异常
            throw "Oh NO";
        }

        let result = main();

        console.log(result);
    </script>

1.返回一个非Promise对象,返回一个新的promise为resolve。

 async function main(){
             return '123';
          }
          let res = main();
          console.log(res);

image.png

2.如果返回是一个Promise对象,由返回结果决定:

如:

 async function main(){
             return new Promise((resolve,reject)=>{
                 reject('NO');
             });
          }
          let res = main();
          console.log(res);

image.png

3.抛出异常也是失败:

 async function main(){
             return new Promise((resolve,reject)=>{
                 reject('NO');
             });
          }
          let res = main();
          console.log(res);

image.png

5.2 await

  1. await 右侧的表达式一般为 promise 对象, 但也可以是其它的值
  2. 如果表达式是 promise 对象, await 返回的是 promise 成功的值
  3. 如果表达式是其它值, 直接将此值作为 await 的返回值 注意:
  4. await 必须写在 async 函数中, 但 async 函数中可以没有 await
  5. 如果 await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理
 <script>
        async function main() {
            let p1 = new Promise((resolve, reject) => {
                resolve('OK');
            })
            let p2 = new Promise((resolve, reject) => {
                reject('Error');
            })
            //1. 右侧为promise的情况
            let res = await p1;
            console.log(res);

            //2. 右侧为其他类型的数据
            let res2 = await 20;
            console.log(res2);

            //3. 如果promise是失败的状态
            try {
                let res3 = await p2;
            } catch (e) {
                console.log(e);
            }
        }

        main();
    </script>

image.png

async与await结合.js实例

/**
 * resource  1.html  2.html 3.html 文件内容
 */

const fs = require('fs');
const util = require('util');
const mineReadFile = util.promisify(fs.readFile);

//回调函数的方式
// fs.readFile('./resource/1.html', (err, data1) => {
//     if(err) throw err;
//     fs.readFile('./resource/2.html', (err, data2) => {
//         if(err) throw err;
//         fs.readFile('./resource/3.html', (err, data3) => {
//             if(err) throw err;
//             console.log(data1 + data2 + data3);
//         });
//     });
// });

//async 与 await
async function main(){
    try{
        //读取第一个文件的内容
        let data1 = await mineReadFile('./resource/1x.html');
        let data2 = await mineReadFile('./resource/2.html');
        let data3 = await mineReadFile('./resource/3.html');
        console.log(data1 + data2 + data3);
    }catch(e){
        console.log(e.code);
    }
}

main();

async与await结合.html实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>async与await结合发送AJAX</title>
</head>
<body>
    <button id="btn">点击获取段子</button>
    <script>
        //封装了一个AJAX回调函数,返回的结果为promise对象
        function sendAJAX(url){
            return new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest();
                xhr.responseType = 'json';
                xhr.open("GET", url);
                xhr.send();
                //处理结果
                xhr.onreadystatechange = function(){
                    if(xhr.readyState === 4){
                        //判断成功
                        if(xhr.status >= 200 && xhr.status < 300){
                            //成功的结果
                            resolve(xhr.response);
                        }else{
                            reject(xhr.status);
                        }
                    }
                }
            });
        }

        //段子接口地址 https://api.apiopen.top/getJoke
        let btn = document.querySelector('#btn');

        btn.addEventListener('click',async function(){
            //获取段子信息
            let duanzi = await sendAJAX('https://api.apiopen.top/getJoke');
            console.log(duanzi);
        });
    </script>
</body>
</html>

5.3 综合练习

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <script>
        function fn1() {
            return Promise.resolve(1)
        }
        function fn2() {
            return 2
        }
        function fn3() {
            return Promise.reject(3)
            // return fn3.test() // 程序运行会抛出异常
        }
        function fn4() {
            return fn3.test() // 程序运行会抛出异常
        }
        // 没有使用 await 的 async 函数
        async function fn5() {
            return 4
        }
        async function fn() {
            // await 右侧是一个成功的 promise
            // const result = await fn1()
            // console.log('result: ', result)
            // return result + 10

            // await 右侧是一个非 promise 的数据
            const result = await fn2()
            console.log('result: ', result)
            return result + 10

            // await 右侧是一个失败的 promise
            // try {
            //     const result = await fn3()
            // } catch (e) {
            //     console.log(e);
            // }

            // await 右侧抛出异常
            // try {
            //     const result = await fn4()
            // } catch (e) {
            //     console.log(e);
            // }
        }
        // fn()

        async function test() {
            try {
                const result2 = await fn()
                console.log('result2', result2)
            } catch (error) {
                console.log('error', error)
            }
            try {
                const result3 = await fn4()
                console.log('result4', result3)
            } catch (error) {
                console.log('error', error)
            }
        }
        test()
    </script>
</body>
</html>

fn()未运行,运行的test()

image.png